مشاركة عبر


استخدام علامات الميزة المتغيرة في تطبيق Python

في هذا البرنامج التعليمي، يمكنك استخدام علامة ميزة متغيرة لإدارة التجارب لمختلف شرائح المستخدم في تطبيق مثال، عرض أسعار اليوم. يمكنك استخدام علامة ميزة المتغير التي تم إنشاؤها في كيفية تغيير علامات الميزات. قبل المتابعة، تأكد من إنشاء علامة ميزة المتغير المسماة Greeting في متجر App Configuration.

المتطلبات الأساسية

إعداد تطبيق ويب Python Flask

إذا كان لديك بالفعل تطبيق ويب Python Flask، يمكنك التخطي إلى قسم استخدام علامة ميزة المتغير.

  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
    

إنشاء عرض أسعار تطبيق اليوم

  1. أنشئ ملفا جديدا باسم app.py في QuoteOfTheDay المجلد بالمحتوى التالي. يقوم بإعداد تطبيق ويب Flask أساسي مع مصادقة المستخدم.

    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. أنشئ ملفا جديدا باسم model.py في المجلد QuoteOfTheDay بالمحتوى التالي. وهو يحدد Quote فئة بيانات ونموذج مستخدم لتطبيق ويب 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 = password
    
  3. أنشئ ملفا جديدا باسم routes.py في المجلد QuoteOfTheDay بالمحتوى التالي. وهو يحدد المسارات لتطبيق الويب Flask، والتعامل مع مصادقة المستخدم وعرض صفحة منزلية مع اقتباس عشوائي.

    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"))
    
  4. أنشئ مجلدا جديدا باسم قوالب في المجلد QuoteOfTheDay وأضف ملفا جديدا باسم base.html فيه بالمحتوى التالي. وهو يحدد صفحة التخطيط لتطبيق الويب.

    <!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. أنشئ ملفا جديدا باسم 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. أنشئ ملفا جديدا باسم 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. أنشئ ملفا جديدا باسم 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 وأضف ملفا جديدا باسم site.css فيه بالمحتوى التالي. يضيف أنماط 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 */
    }
    

استخدام علامة ميزة المتغير

  1. تثبيت أحدث إصدارات الحزم التالية.

    pip install azure-identity 
    pip install azure-appconfiguration-provider
    pip install featuremanagement[AzureMonitor]
    
  2. افتح app.py الملف وأضف التعليمات البرمجية التالية إلى نهاية الملف. يتصل ب App Configuration ويضبط إدارة الميزات.

    يمكنك استخدام DefaultAzureCredential للمصادقة على متجر App Configuration. اتبع الإرشادات لتعيين بيانات الاعتماد الخاصة بك دور قارئ بيانات تكوين التطبيق. تأكد من السماح بوقت كاف للإذن للنشر قبل تشغيل التطبيق الخاص بك.

    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 التعليمات البرمجية التالية وحدثها greeting_message للحصول على متغير الميزة.

    from . import feature_manager
    
    # Update greeting_message to variant
    greeting = feature_manager.get_variant("Greeting", user)
    greeting_message = ""
    if greeting:
        greeting_message = greeting.configuration
    

إنشاء وتشغيل التطبيق

  1. قم بتعيين متغير بيئة يسمى AzureAppConfigurationEndpoint إلى نقطة نهاية متجر App Configuration الموجود ضمن نظرة عامة على متجرك في مدخل Microsoft 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. بمجرد عرض التطبيق قيد التشغيل، حدد Register في أعلى اليمين لتسجيل مستخدم جديد.

    لقطة شاشة لاقتباس تطبيق اليوم، تظهر Register.

  5. تسجيل مستخدم جديد باسم usera@contoso.com.

    إشعار

    من المهم لغرض هذا البرنامج التعليمي استخدام هذه الأسماء بالضبط. طالما تم تكوين الميزة كما هو متوقع، يجب أن يرى المستخدمان متغيرات مختلفة.

  6. حدد الزر إرسال بعد إدخال معلومات المستخدم.

  7. يتم تسجيل دخولك تلقائيا. يجب أن ترى الرسالة usera@contoso.com الطويلة عند عرض التطبيق.

    لقطة شاشة لتطبيق عرض أسعار اليوم، تعرض رسالة خاصة للمستخدم.

  8. تسجيل الخروج باستخدام زر تسجيل الخروج في أعلى اليمين.

  9. تسجيل مستخدم ثان باسم userb@contoso.com.

  10. يتم تسجيل دخولك تلقائيا. يجب أن ترى الرسالة userb@contoso.com القصيرة عند عرض التطبيق.

    لقطة شاشة لتطبيق عرض أسعار اليوم، تعرض رسالة للمستخدم.

الخطوات التالية

للحصول على قائمة تشغيل الميزة الكاملة لمكتبة إدارة ميزات Python، راجع المستند التالي.