Delen via


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

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.

  1. Maak een nieuwe projectmap met de naam QuoteOfTheDay.

  2. Maak een virtuele omgeving in de map QuoteOfTheDay .

    python -m venv venv
    
  3. Activeer de virtuele omgeving.

    .\venv\Scripts\Activate
    
  4. 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

  1. 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)
    
  2. 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
    
  3. 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"))
    
  4. 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">
            &copy; 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>
    
  5. 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 %}
    
  6. 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 %}
    
  7. 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 %}
    
  8. 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

  1. Installeer de nieuwste versies van de volgende pakketten.

    pip install azure-identity 
    pip install azure-appconfiguration-provider
    pip install featuremanagement[AzureMonitor]
    
  2. 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)
    
  3. 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

  1. 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'
    
  2. Voer in de opdrachtprompt, in de map QuoteOfTheDay, het volgende uit: flask run.

  3. Wacht tot de app is gestart en open vervolgens een browser en navigeer naar http://localhost:5000/.

  4. Nadat u de actieve toepassing hebt bekeken, selecteert u Registreren in de rechterbovenhoek om een nieuwe gebruiker te registreren.

    Schermopname van de app Offerte van de dag, waarin Registreren wordt weergegeven.

  5. 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.

  6. Selecteer de knop Verzenden nadat u gebruikersgegevens hebt ingevoerd.

  7. U wordt automatisch aangemeld. U ziet dat usera@contoso.com het lange bericht wordt weergegeven wanneer u de app bekijkt.

    Schermopname van de app Offerte van de dag, met een speciaal bericht voor de gebruiker.

  8. Afmelden met de knop Afmelden in de rechterbovenhoek.

  9. Registreer een tweede gebruiker met de naam userb@contoso.com.

  10. U wordt automatisch aangemeld. U ziet dat userb@contoso.com het korte bericht wordt weergegeven wanneer u de app bekijkt.

    Schermopname van de app Offerte van de dag, met een bericht voor de gebruiker.

Volgende stappen

Raadpleeg het volgende document voor de volledige functierundown van de Python-bibliotheek voor functiebeheer.