Variantfunctievlagmen gebruiken in een Python-toepassing
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.
Vereisten
- Python 3.8 of hoger: zie de Documentatie voor Python in Windows voor meer informatie over het instellen van Python in Windows
- Volg de zelfstudie Over variantfunctievlagmen en maak de vlag voor de variantfunctie met de naam Greeting.
Een Python Flask-web-app instellen
Als u al een Python Flask-web-app hebt, kunt u doorgaan naar de sectie Met de functievlag van de variant.
Maak een nieuwe projectmap met de naam QuoteOfTheDay.
Maak een virtuele omgeving in de map QuoteOfTheDay .
python -m venv venv
Activeer de virtuele omgeving.
.\venv\Scripts\Activate
Installeer de nieuwste versies van de volgende pakketten.
pip install flask pip install flask-login pip install flask_sqlalchemy pip install flask_bcrypt
De prijsopgave van de dag-app maken
Maak een nieuw bestand met de naam
app.py
in deQuoteOfTheDay
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 # 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
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> <!-- 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 %}
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; /* 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 */ }
De vlag voor variantfuncties gebruiken
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") # 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)
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 ... # 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
Ontwikkel de app en voer deze uit
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.
Volgende stappen
Raadpleeg het volgende document voor de volledige functierundown van de Python-bibliotheek voor functiebeheer.