Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In questa esercitazione si usa un flag di funzionalità variante per gestire le esperienze per segmenti utente diversi in un'applicazione di esempio, Offerta del giorno. Usare il flag di funzionalità variant creato in Come variantare i flag di funzionalità. Prima di procedere, assicurarsi di creare il flag di funzionalità variante denominato Messaggio di saluto nell'archivio di Configurazione app.
Prerequisiti
- Python 3.8 o versione successiva: per informazioni sulla configurazione di Python in Windows, vedere la documentazione di Python in Windows
- Seguire l'esercitazione Come variantare i flag di funzionalità e creare il flag di funzionalità variante denominato Messaggio di saluto.
Configurare un'app Web Python Flask
Se si dispone già di un'app Web Python Flask, è possibile passare alla sezione Usare il flag di funzionalità variant.
Creare una nuova cartella di progetto denominata QuoteOfTheDay.
Creare un ambiente virtuale nella cartella QuoteOfTheDay.
python -m venv venvAttivare l'ambiente virtuale.
.\venv\Scripts\ActivateInstallare le versioni più recenti dei pacchetti seguenti.
pip install flask pip install flask-login pip install flask_sqlalchemy pip install flask_bcrypt
Creare l'app Quote of the Day
Creare un nuovo file denominato
app.pynella cartellaQuoteOfTheDaycon il contenuto seguente. Configura un'applicazione Web Flask di base con l'autenticazione utente.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)Creare un nuovo file denominato model.py nella cartella QuoteOfTheDay con il contenuto seguente. Definisce una classe di dati
Quotee un modello utente per l'applicazione Web Flask.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 = passwordCreare un nuovo file denominato routes.py nella cartella QuoteOfTheDay con il contenuto seguente. Definisce le route per l'applicazione Web Flask, la gestione dell'autenticazione utente e la visualizzazione di una home page con un'offerta casuale.
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"]) def index(): context = {} user = "" if current_user.is_authenticated: user = current_user.username context["user"] = user else: context["user"] = "Guest" 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"))Creare una nuova cartella denominata templates nella cartella QuoteOfTheDay e aggiungere un nuovo file denominato base.html al suo interno con il contenuto seguente. Definisce la pagina di layout per l'applicazione Web.
<!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>Creare un nuovo file denominato index.html nella cartella templates con il contenuto seguente. Estende il modello di base e aggiunge il blocco di contenuto.
{% 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 %}Creare un nuovo file denominato sign_up.html nella cartella templates con il contenuto seguente. Definisce il modello per la pagina di registrazione dell'utente.
{% 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 %}Creare un nuovo file denominato login.html nella cartella templates con il contenuto seguente. Definisce il modello per la pagina di accesso dell'utente.
{% 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 %}Creare una nuova cartella denominata static nella cartella QuoteOfTheDay e aggiungere un nuovo file denominato site.css in esso con il contenuto seguente. Aggiunge stili CSS per l'applicazione Web.
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 */ }
Usare il flag di funzionalità variant
Installare le versioni più recenti dei pacchetti seguenti.
pip install azure-identity pip install azure-appconfiguration-provider pip install featuremanagement[AzureMonitor]Aprire il file
app.pye aggiungere il codice seguente alla fine del file. Si connette a Configurazione app e configura la gestione delle funzionalità.Usare
DefaultAzureCredentialper eseguire l'autenticazione nell'archivio di Configurazione app. Seguire le istruzioni per assegnare le credenziali al ruolo Lettore dati di Configurazione app. Assicurarsi di consentire tempo sufficiente per la propagazione dell'autorizzazione prima di eseguire l'applicazione.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)Aprire
routes.pye aggiornare il codice seguente ingreeting_messageper ottenere la variante della funzionalità.from . import feature_manager # Update greeting_message to variant greeting = feature_manager.get_variant("Greeting", user) greeting_message = "" if greeting: greeting_message = greeting.configuration
Compilare ed eseguire l'app
Impostare una variabile di ambiente denominata AzureAppConfigurationEndpoint sull'endpoint dell'archivio di Configurazione app disponibile in Panoramica dell'archivio nel portale di Azure.
Se si usa il prompt dei comandi di Windows, eseguire il comando seguente e riavviare il prompt per rendere effettiva la modifica:
setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"Se si usa PowerShell, eseguire il comando seguente:
$Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"Se si usa macOS o Linux, eseguire il comando seguente:
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'Nel prompt dei comandi, nella cartella QuoteOfTheDay eseguire:
flask run.Attendere l'avvio dell'app e quindi aprire un browser e passare a
http://localhost:5000/.Una volta che si sta visualizzando l'applicazione in esecuzione, selezionare Registra in alto a destra per registrare un nuovo utente.
Registrare un nuovo utente denominato usera@contoso.com.
Note
Ai fini di questa esercitazione è importante usare esattamente questi nomi. Se la funzionalità è stata configurata come previsto, i due utenti dovrebbero visualizzare varianti diverse.
Selezionare il pulsante Invia dopo aver immesso le informazioni utente.
L'accesso viene eseguito automaticamente. Dovresti vedere che usera@contoso.com vede il messaggio lungo quando si visualizza l'app.
Disconnettersi con il pulsante Logout in alto a destra.
Registrare un secondo utente denominato userb@contoso.com.
L'accesso viene eseguito automaticamente. Si noterà che userb@contoso.com viene visualizzato il breve messaggio quando si visualizza l'app.
Passaggi successivi
Per il rundown completo delle funzionalità della libreria di gestione delle funzionalità Python, vedere il documento seguente.