Använda variantfunktionsflaggor i ett Python-program
I den här självstudien använder du en variantfunktionsflagga för att hantera upplevelser för olika användarsegment i ett exempelprogram, Quote of the Day. Du använder den variantfunktionsflagga som skapats i Så här varianterar du funktionsflaggor. Innan du fortsätter måste du skapa variantfunktionsflaggan med namnet Greeting (Hälsning ) i appkonfigurationsarkivet.
Förutsättningar
- Python 3.8 eller senare – information om hur du konfigurerar Python i Windows finns i dokumentationen om Python i Windows
- Följ självstudiekursen Så här varianterar du funktionsflaggor och skapa variantfunktionsflaggan med namnet Greeting (Hälsning).
Konfigurera en Python Flask-webbapp
Om du redan har en Python Flask-webbapp kan du gå vidare till avsnittet Använd variantfunktionsflaggan .
Skapa en ny projektmapp med namnet QuoteOfTheDay.
Skapa en virtuell miljö i mappen QuoteOfTheDay .
python -m venv venv
Aktivera den virtuella miljön.
.\venv\Scripts\Activate
Installera de senaste versionerna av följande paket.
pip install flask pip install flask-login pip install flask_sqlalchemy pip install flask_bcrypt
Skapa appen Offert för dagen
Skapa en ny fil med namnet
app.py
iQuoteOfTheDay
mappen med följande innehåll. Den konfigurerar ett grundläggande Flask-webbprogram med användarautentisering.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)
Skapa en ny fil med namnet model.py i mappen QuoteOfTheDay med följande innehåll. Den definierar en
Quote
dataklass och en användarmodell för Flask-webbprogrammet.from dataclasses import dataclass from flask_login import UserMixin from . import db @dataclass class Quote: message: str author: str # Create user model 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
Skapa en ny fil med namnet routes.py i mappen QuoteOfTheDay med följande innehåll. Den definierar vägar för Flask-webbprogrammet, hanterar användarautentisering och visar en startsida med ett slumpmässigt citattecken.
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"))
Skapa en ny mapp med namnet mallar i mappen QuoteOfTheDay och lägg till en ny fil med namnet base.html i den med följande innehåll. Den definierar layoutsidan för webbprogrammet.
<!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>
Skapa en ny fil med namnet index.html i mallmappen med följande innehåll. Den utökar basmallen och lägger till innehållsblocket.
{% 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> <!-- Heart icon --> </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 %}
Skapa en ny fil med namnet sign_up.html i mallmappen med följande innehåll. Den definierar mallen för sidan för användarregistrering.
{% 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 %}
Skapa en ny fil med namnet login.html i mallmappen med följande innehåll. Den definierar mallen för användarens inloggningssida.
{% 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 %}
Skapa en ny mapp med namnet static i mappen QuoteOfTheDay och lägg till en ny fil med namnet site.css i den med följande innehåll. Den lägger till CSS-format för webbprogrammet.
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; /* White background */ border-color: #ffffff; /* Light blue border */ color: #333 } .vote-container .btn:focus { outline: none; box-shadow: none; } .vote-container .btn:hover { background-color: #F0F0F0; /* Light gray background */ } .greeting-content { font-family: 'Georgia', serif; /* More artistic font */ } .quote-content p.quote { font-size: 2em; /* Bigger font size */ font-family: 'Georgia', serif; /* More artistic font */ font-style: italic; /* Italic font */ color: #4EC2F7; /* Medium-light blue color */ }
Använd variantfunktionsflaggan
Installera de senaste versionerna av följande paket.
pip install azure-identity pip install azure-appconfiguration-provider pip install featuremanagement[AzureMonitor]
app.py
Öppna filen och lägg till följande kod i slutet av filen. Den ansluter till App Configuration och konfigurerar funktionshantering.Du använder för att autentisera
DefaultAzureCredential
till appkonfigurationsarkivet. Följ anvisningarna för att tilldela dina autentiseringsuppgifter rollen App Configuration Data Reader. Se till att ge tillräckligt med tid för att behörigheten ska spridas innan du kör programmet.import os from azure.appconfiguration.provider import load from featuremanagement import FeatureManager from azure.identity import DefaultAzureCredential ENDPOINT = os.getenv("AzureAppConfigurationEndpoint") # Updates the flask app configuration with the Azure App Configuration settings whenever a refresh happens def callback(): app.config.update(azure_app_config) # Connect to App Configuration 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) # Create a FeatureManager feature_manager = FeatureManager(azure_app_config)
Öppna
routes.py
och lägg till följande kod i slutet av den för att uppdatera konfigurationen och hämta funktionsvarianten.from featuremanagement.azuremonitor import track_event from . import azure_app_config, feature_manager ... # Update the post request to track liked events if request.method == "POST": track_event("Liked", user) return redirect(url_for("pages.index")) ... # Update greeting_message to variant greeting = feature_manager.get_variant("Greeting", user) greeting_message = "" if greeting: greeting_message = greeting.configuration
Kompilera och köra appen
Ange en miljövariabel med namnet AzureAppConfigurationEndpoint till slutpunkten för appkonfigurationsarkivet som finns under Översikt över din butik i Azure Portal.
Om du använder Windows-kommandotolken kör du följande kommando och startar om kommandotolken så att ändringen börjar gälla:
setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"
Om du använder PowerShell kör du följande kommando:
$Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
Om du använder macOS eller Linux kör du följande kommando:
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
I kommandotolken i mappen QuoteOfTheDay kör du:
flask run
.Vänta tills appen startas och öppna sedan en webbläsare och navigera till
http://localhost:5000/
.När du visar det program som körs väljer du Registrera längst upp till höger för att registrera en ny användare.
Registrera en ny användare med namnet usera@contoso.com.
Kommentar
Det är viktigt för den här självstudien att använda dessa namn exakt. Så länge funktionen har konfigurerats som förväntat bör de två användarna se olika varianter.
Välj knappen Skicka när du har angett användarinformation.
Du är automatiskt inloggad. Du bör se det usera@contoso.com långa meddelandet när du visar appen.
Logga ut med knappen Logga ut längst upp till höger.
Registrera en andra användare med namnet userb@contoso.com.
Du är automatiskt inloggad. Du bör se det userb@contoso.com korta meddelandet när du visar appen.
Nästa steg
Fullständig funktionskörning i Python-funktionshanteringsbiblioteket finns i följande dokument.