共用方式為


在 Python 應用程式中使用變體功能旗標

在本教學課程中,您會使用變體功能旗標來管理範例應用程式中不同使用者區段的體驗, 「日引述」。 您可以使用如何在變化功能旗標中 建立的 Variant 功能旗標。 繼續之前,請確定您在 應用程式組態 存放區中建立名為 Greeting 的變體功能旗標。

必要條件

設定 Python Flask Web 應用程式

如果您已經有 Python Flask Web 應用程式,您可以跳至 使用 Variant 功能旗標 區段。

  1. 建立名為 QuoteOfTheDay 的新項目資料夾。

  2. QuoteOfTheDay 資料夾中建立虛擬環境。

    python -m venv venv
    
  3. 啟用虛擬環境。

    .\venv\Scripts\Activate
    
  4. 安裝下列套件的最新版本。

    pip install flask
    pip install flask-login
    pip install flask_sqlalchemy
    pip install flask_bcrypt
    

建立 Day 應用程式的報價

  1. 使用下列內容,在 QuoteOfTheDay 資料夾中建立名為 app.py 的新檔案。 它會使用使用者驗證來設定基本的 Flask Web 應用程式。

    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. 使用下列內容,在QuoteOfTheDay資料夾中建立名為 model.py 的新檔案。 它會定義 Quote Flask Web 應用程式的數據類別和使用者模型。

    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. 在 QuoteOfTheDay 資料夾中建立名為 routes.py的新檔案,其中包含下列內容。 它會定義 Flask Web 應用程式的路由、處理使用者驗證,以及顯示具有隨機引號的首頁。

    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. 在 QuoteOfTheDay 資料夾中建立名為 templates 的新資料夾,並在其中新增名為 base.html 的新檔案,其中包含下列內容。 它會定義 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">
            &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. 使用下列內容,在templates資料夾中建立名為 index.html 的新檔案。 它會擴充基底範本,並新增內容區塊。

    {% 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. 使用下列內容,在templates資料夾中建立名為 sign_up.html 的新檔案。 它會定義用戶註冊頁面的範本。

    {% 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. 使用下列內容,在templates資料夾中建立名為 login.html 的新檔案。 它會定義使用者登入頁面的範本。

    {% 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. 在 QuoteOfTheDay 資料夾中建立名為 static 的新資料夾,並在其中新增名為 site.css的新檔案,其中包含下列內容。 它會新增 Web 應用程式的 CSS 樣式。

    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 */
    }
    

使用 Variant 功能旗標

  1. 安裝下列套件的最新版本。

    pip install azure-identity 
    pip install azure-appconfiguration-provider
    pip install featuremanagement[AzureMonitor]
    
  2. 開啟 檔案, app.py 並將下列程式代碼新增至檔案結尾。 它會連線到 應用程式組態 並設定功能管理。

    您可以使用 DefaultAzureCredential 來向 應用程式組態 存放區進行驗證。 請遵循指示來指派認證 應用程式組態 數據讀取者角色。 在執行應用程式之前,請務必允許足夠的時間來傳播許可權。

    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. 開啟 routes.py 並將下列程式代碼新增至其結尾,以重新整理組態並取得功能變體。

    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
    

建置並執行應用程式

  1. 將名為 AzureAppConfigurationEndpoint 的環境變數設定為您在 Azure 入口網站 中存放區概觀底下找到的 應用程式組態 存放區端點。

    如果您使用 Windows 命令提示字元,請執行下列命令,然後重新啟動命令提示字元以讓變更生效:

    setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"
    

    如果您使用 PowerShell,請執行下列命令:

    $Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
    

    如果您使用 macOS 或 Linux,請執行下列命令:

    export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
    
  2. 在命令提示字元中,於 QuoteOfTheDay 資料夾內執行:flask run

  3. 等候應用程式啟動,然後開啟瀏覽器並流覽至 http://localhost:5000/

  4. 檢視執行中的應用程式之後,選取右上方的 [註冊] 以註冊新的使用者。

    螢幕擷取畫面:顯示 [註冊] 的 Quote of the day 應用程式。

  5. 註冊名為 usera@contoso.com 的新使用者。

    注意

    請務必讓本教學課程正確使用這些名稱。 只要功能如預期般設定,這兩個使用者應該會看到不同的變體。

  6. 輸入使用者資訊之後,選取 [ 提交 ] 按鈕。

  7. 您會自動登入。 您應該會在檢視應用程式時看到 usera@contoso.com 長訊息。

    螢幕擷取畫面:顯示使用者特殊訊息的 Quote of the day 應用程式。

  8. 使用右上方的 [註銷] 按鈕註銷

  9. 註冊名為 userb@contoso.com的第二個使用者。

  10. 您會自動登入。 您應該會在檢視應用程式時看到 userb@contoso.com 簡短訊息。

    [每日報價] 應用程式的螢幕快照,其中顯示使用者的訊息。

下一步

如需 Python 功能管理連結庫的完整功能取消,請參閱下列檔。