Dela via


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

Konfigurera en Python Flask-webbapp

Om du redan har en Python Flask-webbapp kan du gå vidare till avsnittet Använd variantfunktionsflaggan .

  1. Skapa en ny projektmapp med namnet QuoteOfTheDay.

  2. Skapa en virtuell miljö i mappen QuoteOfTheDay .

    python -m venv venv
    
  3. Aktivera den virtuella miljön.

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

  1. Skapa en ny fil med namnet app.py i QuoteOfTheDay 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)
    
  2. 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
    
  3. 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"))
    
  4. 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">
            &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. 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 %}
    
  6. 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 %}
    
  7. 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 %}
    
  8. 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

  1. Installera de senaste versionerna av följande paket.

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

  1. 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'
    
  2. I kommandotolken i mappen QuoteOfTheDay kör du: flask run.

  3. Vänta tills appen startas och öppna sedan en webbläsare och navigera till http://localhost:5000/.

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

    Skärmbild av appen Offert för dagen som visar Registrera.

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

  6. Välj knappen Skicka när du har angett användarinformation.

  7. Du är automatiskt inloggad. Du bör se det usera@contoso.com långa meddelandet när du visar appen.

    Skärmbild av appen Offert för dagen som visar ett särskilt meddelande för användaren.

  8. Logga ut med knappen Logga ut längst upp till höger.

  9. Registrera en andra användare med namnet userb@contoso.com.

  10. Du är automatiskt inloggad. Du bör se det userb@contoso.com korta meddelandet när du visar appen.

    Skärmbild av appen Offert för dagen som visar ett meddelande för användaren.

Nästa steg

Fullständig funktionskörning i Python-funktionshanteringsbiblioteket finns i följande dokument.