In deze zelfstudie gebruikt u een vlag voor variantfuncties voor het beheren van ervaringen voor verschillende gebruikerssegmenten in een voorbeeldtoepassing, Quote of the Day. U gebruikt de functievlag van de variant die is gemaakt in Functievlagmen van de procedure. Voordat u doorgaat, moet u ervoor zorgen dat u de variantfunctievlag Begroeting maakt in uw App Configuration-archief.
Maak een nieuw bestand met de naam app.py
in de QuoteOfTheDay
map met de volgende inhoud. Hiermee stelt u een eenvoudige Flask-webtoepassing in met gebruikersverificatie.
from flask_bcrypt import Bcrypt
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask import Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
bcrypt = Bcrypt(app)
db = SQLAlchemy()
db.init_app(app)
login_manager = LoginManager()
login_manager.init_app(app)
from .model import Users
@login_manager.user_loader
def loader_user(user_id):
return Users.query.get(user_id)
with app.app_context():
db.create_all()
if __name__ == "__main__":
app.run(debug=True)
from . import routes
app.register_blueprint(routes.bp)
Maak een nieuw bestand met de naam model.py in de map QuoteOfTheDay met de volgende inhoud. Hiermee definieert u een Quote
gegevensklasse en een gebruikersmodel voor de Flask-webtoepassing.
from dataclasses import dataclass
from flask_login import UserMixin
from . import db
@dataclass
class Quote:
message: str
author: str
class Users(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(250), unique=True, nullable=False)
password_hash = db.Column(db.String(250), nullable=False)
def __init__(self, username, password):
self.username = username
self.password_hash = password
Maak een nieuw bestand met de naam routes.py in de map QuoteOfTheDay met de volgende inhoud. Het definieert routes voor de Flask-webtoepassing, het verwerken van gebruikersverificatie en het weergeven van een startpagina met een willekeurige aanhalingsteken.
import random
from flask import Blueprint, render_template, request, flash, redirect, url_for
from flask_login import current_user, login_user, logout_user
from . import db, bcrypt
from .model import Quote, Users
bp = Blueprint("pages", __name__)
@bp.route("/", methods=["GET", "POST"])
def index():
context = {}
user = ""
if current_user.is_authenticated:
user = current_user.username
context["user"] = user
else:
context["user"] = "Guest"
if request.method == "POST":
return redirect(url_for("pages.index"))
quotes = [
Quote("You cannot change what you are, only what you do.", "Philip Pullman"),
]
greeting_message = "Hi"
context["model"] = {}
context["model"]["greeting_message"] = greeting_message
context["model"]["quote"] = {}
context["model"]["quote"] = random.choice(quotes)
context["isAuthenticated"] = current_user.is_authenticated
return render_template("index.html", **context)
@bp.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
password = request.form.get("password")
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
user = Users(request.form.get("username"), hashed_password)
try:
db.session.add(user)
db.session.commit()
except Exception as e:
flash("Username already exists")
return redirect(url_for("pages.register"))
login_user(user)
return redirect(url_for("pages.index"))
return render_template("sign_up.html")
@bp.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
user = Users.query.filter_by(username=request.form.get("username")).first()
password = request.form.get("password")
if user and bcrypt.check_password_hash(user.password_hash, password):
login_user(user)
return redirect(url_for("pages.index"))
return render_template("login.html")
@bp.route("/logout")
def logout():
logout_user()
return redirect(url_for("pages.index"))
Maak een nieuwe map met de naam sjablonen in de map QuoteOfTheDay en voeg een nieuw bestand met de naam base.html toe met de volgende inhoud. Hiermee definieert u de indelingspagina voor de webtoepassing.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>QuoteOfTheDay</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='site.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" href="/">QuoteOfTheDay</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" href="/">Home</a>
</li>
</ul>
{% block login_partial %}
<ul class="navbar-nav">
{% if isAuthenticated %}
<li class="nav-item">
<a class="nav-link text-dark">Hello {{user}}!</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="/logout">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link text-dark" href="/register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="/login">Login</a>
</li>
{% endif %}
</ul>
{% endblock %}
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
{% block content %}
{% endblock %}
</main>
</div>
</body>
<footer class="border-top footer text-muted">
<div class="container">
© 2024 - QuoteOfTheDay
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>
Maak een nieuw bestand met de naam index.html in de map sjablonen met de volgende inhoud. Het breidt de basissjabloon uit en voegt het inhoudsblok toe.
{% extends 'base.html' %}
{% block content %}
<div class="quote-container">
<div class="quote-content">
{% if model.greeting_message %}
<h3 class="greeting-content">{{model.greeting_message}}</h3>
{% endif %}
<br />
<p class="quote">“{{model.quote.message}}”</p>
<p>- <b>{{model.quote.author}}</b></p>
</div>
<div class="vote-container">
<button class="btn btn-primary" onclick="heartClicked(this)">
<i class="far fa-heart"></i>
</button>
</div>
<form action="/" method="post">
</form>
</div>
<script>
function heartClicked(button) {
var icon = button.querySelector('i');
icon.classList.toggle('far');
icon.classList.toggle('fas');
}
</script>
{% endblock %}
Maak een nieuw bestand met de naam sign_up.html in de map sjablonen met de volgende inhoud. Hiermee definieert u de sjabloon voor de gebruikersregistratiepagina.
{% extends 'base.html' %}
{% block content %}
<div class="login-container">
<h1>Create an account</h1>
<form action="#" method="post">
<label for="username">Username:</label>
<input type="text" name="username" />
<label for="password">Password:</label>
<input type="password" name="password" />
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
Maak een nieuw bestand met de naam login.html in de map sjablonen met de volgende inhoud. Hiermee definieert u de sjabloon voor de aanmeldingspagina van de gebruiker.
{% extends 'base.html' %}
{% block content %}
<div class="login-container">
<h1>Login to your account</h1>
<form action="#" method="post">
<label for="username">Username:</label>
<input type="text" name="username" />
<label for="password">Password:</label>
<input type="password" name="password" />
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
Maak een nieuwe map met de naam Statisch in de map QuoteOfTheDay en voeg een nieuw bestand met de naam site.css toe met de volgende inhoud. Hiermee worden CSS-stijlen toegevoegd voor de webtoepassing.
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
}
.quote-container {
background-color: #fff;
margin: 2em auto;
padding: 2em;
border-radius: 8px;
max-width: 750px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
display: flex;
justify-content: space-between;
align-items: start;
position: relative;
}
.login-container {
background-color: #fff;
margin: 2em auto;
padding: 2em;
border-radius: 8px;
max-width: 750px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
justify-content: space-between;
align-items: start;
position: relative;
}
.vote-container {
position: absolute;
top: 10px;
right: 10px;
display: flex;
gap: 0em;
}
.vote-container .btn {
background-color: #ffffff;
border-color: #ffffff;
color: #333
}
.vote-container .btn:focus {
outline: none;
box-shadow: none;
}
.vote-container .btn:hover {
background-color: #F0F0F0;
}
.greeting-content {
font-family: 'Georgia', serif;
}
.quote-content p.quote {
font-size: 2em;
font-family: 'Georgia', serif;
font-style: italic;
color: #4EC2F7;
}
Installeer de nieuwste versies van de volgende pakketten.
pip install azure-identity
pip install azure-appconfiguration-provider
pip install featuremanagement[AzureMonitor]
Open het app.py
bestand en voeg de volgende code toe aan het einde van het bestand. Het maakt verbinding met App Configuration en stelt functiebeheer in.
U gebruikt de DefaultAzureCredential
app om u te verifiëren bij uw App Configuration-archief. Volg de instructies om uw referenties toe te wijzen aan de rol App Configuration Data Reader . Zorg ervoor dat u voldoende tijd hebt om de machtiging door te geven voordat u de toepassing uitvoert.
import os
from azure.appconfiguration.provider import load
from featuremanagement import FeatureManager
from azure.identity import DefaultAzureCredential
ENDPOINT = os.getenv("AzureAppConfigurationEndpoint")
def callback():
app.config.update(azure_app_config)
global azure_app_config
azure_app_config = load(
endpoint=ENDPOINT,
credential=DefaultAzureCredential(),
on_refresh_success=callback,
feature_flag_enabled=True,
feature_flag_refresh_enabled=True,
)
app.config.update(azure_app_config)
feature_manager = FeatureManager(azure_app_config)
Open routes.py
en voeg de volgende code toe aan het einde van de code om de configuratie te vernieuwen en de functievariant op te halen.
from featuremanagement.azuremonitor import track_event
from . import azure_app_config, feature_manager
...
if request.method == "POST":
track_event("Liked", user)
return redirect(url_for("pages.index"))
...
greeting = feature_manager.get_variant("Greeting", user)
greeting_message = ""
if greeting:
greeting_message = greeting.configuration
Stel een omgevingsvariabele met de naam AzureAppConfigurationEndpoint in op het eindpunt van uw App Configuration-archief in het overzicht van uw winkel in Azure Portal.
Als u de Windows-opdrachtprompt gebruikt, voert u de volgende opdracht uit en start u de opdrachtprompt opnieuw om de wijziging door te voeren:
setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"
Als u PowerShell gebruikt, voert u de volgende opdracht uit:
$Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
Als u macOS of Linux gebruikt, voert u de volgende opdracht uit:
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
Voer in de opdrachtprompt, in de map QuoteOfTheDay, het volgende uit: flask run
.
Wacht tot de app is gestart en open vervolgens een browser en navigeer naar http://localhost:5000/
.
Nadat u de actieve toepassing hebt bekeken, selecteert u Registreren in de rechterbovenhoek om een nieuwe gebruiker te registreren.
Registreer een nieuwe gebruiker met de naam usera@contoso.com.
Notitie
Het is belangrijk voor deze zelfstudie om deze namen precies te gebruiken. Zolang de functie is geconfigureerd zoals verwacht, moeten de twee gebruikers verschillende varianten zien.
Selecteer de knop Verzenden nadat u gebruikersgegevens hebt ingevoerd.
U wordt automatisch aangemeld. U ziet dat usera@contoso.com het lange bericht wordt weergegeven wanneer u de app bekijkt.
Afmelden met de knop Afmelden in de rechterbovenhoek.
Registreer een tweede gebruiker met de naam userb@contoso.com.
U wordt automatisch aangemeld. U ziet dat userb@contoso.com het korte bericht wordt weergegeven wanneer u de app bekijkt.
Raadpleeg het volgende document voor de volledige functierundown van de Python-bibliotheek voor functiebeheer.