Skip to content

36. تمرين تطبيقي: الإصدار 16

Image

36.1. مقدمة

تتخذ URL في تطبيقنا حاليًا الشكل [/action/param1/param2/…]. نود أن نتمكن من إضافة بادئة إلى هذه الرموز URL. على سبيل المثال، باستخدام البادئة [/do]، سنحصل على رموز URL بالصيغة [/do/action/param1/param2/…].

36.2. التكوين الجديد للمسارات

Image

  • إلى [2]، سيتم تعديل حساب المسارات؛
  • في [1]، يتم تعديل الملف [config] ليعكس هذا التغيير؛

يصبح التكوين [config] كما يلي:


def configure(config: dict) -> dict:
    # تكوين مسار النظام
    import syspath
    config['syspath'] = syspath.configure(config)

    # إعدادات التطبيق
    import parameters
    config['parameters'] = parameters.configure(config)

    # تكوين قاعدة البيانات
    import database
    config["database"] = database.configure(config)

    # إنشاء مثيلات طبقات التطبيق
    import layers
    config['layers'] = layers.configure(config)

    # تكوين MVC لطبقة [web]
    config['mvc'] = {}

    # تكوين وحدات التحكم في الطبقة [web]
    import controllers
    config['mvc'].update(controllers.configure(config))

    # الإجراءات ASV (إجراء عرض)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    # الإجراءات ADS (إجراء "Do Something")
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    # تكوين الردود HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    # تكوين المسارات
    import routes
    routes.configure(config)

    # تطبيق التكوين
    return config
  • الأسطر 37-39: تتولى الوحدة النمطية [routes] (السطر 38) تكوين المسارات (السطر 39)؛

يتغير ملف المسارات بدون رمز CSRF [configs/routes_without_csrftoken] على النحو التالي:


# التبعيات

from flask import redirect, request, session, url_for
from flask_api import status

# تكوين التطبيق
config = {}

# وحدة التحكم الأمامية
def front_controller() -> tuple:
    # يتم توجيه الطلب إلى وحدة التحكم الرئيسية
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# جذر التطبيق
def index() -> tuple:
    # إعادة التوجيه إلى /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

# بدء الجلسة
def init_session(type_response: str) -> tuple:
    # يتم تنفيذ وحدة التحكم المرتبطة بالإجراء
    return front_controller()

# توثيق المستخدم
def authentifier_utilisateur() -> tuple:
    # يتم تنفيذ وحدة التحكم المرتبطة بالإجراء
    return front_controller()

# حساب الضريبة
def calculer_impot() -> tuple:
    # يتم تنفيذ وحدة التحكم المرتبطة بالإجراء
    return front_controller()


تمت إزالة المسارات من الملف. ولم يتبق سوى الوظائف المرتبطة بها.

ويخضع ملف المسارات الذي يحتوي على الرموز CSRF و [configs/routes_with_csrftoken] لنفس المصير:


# التبعيات

from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf

# التكوين
config = {}

# وحدة التحكم الأمامية
def front_controller() -> tuple:
    # يتم توجيه الطلب إلى وحدة التحكم الرئيسية
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# جذر التطبيق
def index() -> tuple:
    # إعادة التوجيه إلى /init-session/html
    return redirect(url_for("init_session", type_response="html"csrf_token=generate_csrf()), status.HTTP_302_FOUND)

# init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
    # يتم تنفيذ وحدة التحكم المرتبطة بالإجراء
    return front_controller()

# توثيق المستخدم
def authentifier_utilisateur(csrf_token: str) -> tuple:
    # يتم تنفيذ وحدة التحكم المرتبطة بالإجراء
    return front_controller()



# init-session-without-csrftoken لعملاء json و xml
def init_session_without_csrftoken(type_response: str) -> tuple:
    # إعادة التوجيه إلى /init-session/type_response
    return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
                    status.HTTP_302_FOUND)

يتم حساب المسارات بواسطة الوحدة النمطية [configs/routes] التالية:


from flask import Flask

def configure(config: dict):
    # تكوين المسارات

    # تطبيق Flask
    app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
    config['app'] = app

    # استيراد مسارات تطبيق الويب
    if config['parameters']['with_csrftoken']:
        import routes_with_csrftoken as routes
    else:
        import routes_without_csrftoken as routes

    # إدراج التكوين في المسارات
    routes.config = config

    # البادئة الخاصة بـ URL للتطبيق
    prefix_url = config["parameters"]["prefix_url"]

    # رمز CSRF
    with_csrftoken = config["parameters"]['with_csrftoken']
    if with_csrftoken:
        csrftoken_param = f"/<string:csrf_token>"
    else:
        csrftoken_param = ""

    # مسارات تطبيق Flask
    # جذر التطبيق
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

    # تبدء الجلسة
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

    # بدء الجلسة بدون رمز CSRF
    if with_csrftoken:
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken/<string:type_response>',
                         methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)

    # توثيق-المستخدم
    app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
                     view_func=routes.authentifier_utilisateur)

    # حساب الضريبة
    app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impot)

    # حساب الضريبة على دفعات
    app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impots)

    # lister-simulations
    app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.lister_simulations)

    # حذف-المحاكاة
    app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
                     view_func=routes.supprimer_simulation)

    # إنهاء-الجلسة
    app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
                     view_func=routes.fin_session)

    # عرض حساب الضريبة
    app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_calcul_impot)

    # الحصول على بيانات المسؤول
    app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
                     view_func=routes.get_admindata)

    # عرض-نظرة-حساب-الضرائب
    app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_calcul_impot)

    # عرض-طريقة-المصادقة
    app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_authentification)

    # عرض-طريقة-عرض-قائمة-المحاكاة
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_simulations)

    # عرض-قائمة_الأخطاء
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_erreurs)

    # حالة خاصة
    if with_csrftoken:
        # بدء-الجلسة-بدون-رمز-CSRF لعملاء JSON و XML
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)
  • الأسطر 6-8: يتم إنشاء تطبيق Flask وإدراجه في التكوين؛
  • الأسطر 10-14: يتم استيراد ملف المسارات المناسب للحالة. تجدر الإشارة إلى أن الملف المستورد لا يحتوي في الواقع على أي مسارات. فهو لا يحتوي سوى على الدوال المرتبطة بها؛
  • السطر 17: تحتاج الوظائف المرتبطة بمسارات التوجيه إلى معرفة تكوين التطبيق؛
  • السطر 20: نلاحظ البادئة URL. يمكن أن تكون هذه البادئة فارغة؛
  • الأسطر 22-27: المسارات التي تحتوي على الرمز المميز CSRF لها معلمة إضافية مقارنة بتلك التي لا تحتوي عليه. لإدارة هذا الاختلاف، نستخدم المتغير [csrftoken_param]:
    • تحتوي على سلسلة فارغة إذا لم تكن هناك علامة CSRF في المسارات؛
    • وتحتوي على السلسلة [/<string:csrf_token>] في حالة وجود الرمز CSRF؛
  • الأسطر 29-96: يتم ربط كل مسار في التطبيق بوظيفة من ملف المسارات المستورد في الأسطر 10-14؛

36.3. وحدات التحكم الجديدة

Image

في الإصدار السابق، كانت جميع وحدات التحكم تحصل على الإجراء قيد المعالجة بالطريقة التالية:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # استرداد عناصر المسار
        params = request.path.split('/')
        action = params[1]

لم يعد هذا الرمز يعمل في حالة وجود بادئة، على سبيل المثال [/do/lister-simulations]. في هذه الحالة، ستكون العملية، في السطر 3، هي [do]، وبالتالي ستكون غير صحيحة.

نقوم بتحويل هذا الرمز على النحو التالي:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # استرداد عناصر المسار
        prefix_url = config["parameters"]["prefix_url"]
        params = request.path[len(prefix_url):].split('/')
        action = params[1]

يتم إجراء ذلك في جميع وحدات التحكم.

36.4. النماذج الجديدة

Image

في الإصدار السابق، كانت كل فئة من فئات القوالب تُنشئ قالبًا بالطريقة التالية:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # تغليف بيانات الصفحة في قالب
        modèle = {}
        # حالة التطبيق
        état = résultat["état"]
        # يعتمد النموذج على الحالة
        

        # رمز CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # الإجراءات الممكنة من العرض
        modèle['actions_possibles'] = []

        # يتم عرض النموذج
        return modèle

من الآن فصاعدًا، سيحتوي النموذج على مفتاح إضافي، وهو البادئة URL:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # تغليف بيانات الصفحة في النموذج
        modèle = {}
        # حالة التطبيق
        état = résultat["état"]
        # يعتمد النموذج على الحالة
        

        # رمز CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # الإجراءات الممكنة من العرض
        modèle['actions_possibles'] = []
    
        # بادئة URL
        modèle["prefix_url"] = config["parameters"]["prefix_url"]

        # إرجاع النموذج
        return modèle

36.5. الأجزاء الجديدة

Image

يجب تعديل جميع الأجزاء التي تحتوي على URL.

الجزء [v-authentification]


<!-- نموذج HTML - يتم إرسال قيمه باستخدام الإجراء [authentifier-utilisateur] -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">

    <!-- العنوان -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>


</form>

الجزء [v-calcul-impot]


<!-- نموذج HTML الذي تم إرساله -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
    <!-- رسالة مكونة من 12 عمودًا على خلفية زرقاء -->
    

</form>

المقطع [v-liste-simulations]




{% if modèle.simulations is defined and modèle.simulations|length!=0 %}


<!-- جدول المحاكاة -->
<table class="table table-sm table-hover table-striped">
    
    <tr>
        <th scope="row">{{simulation.id}}</th>
        <td>{{simulation.marié}}</td>
        <td>{{simulation.enfants}}</td>
        <td>{{simulation.salaire}}</td>
        <td>{{simulation.impôt}}</td>
        <td>{{simulation.surcôte}}</td>
        <td>{{simulation.décôte}}</td>
        <td>{{simulation.réduction}}</td>
        <td>{{simulation.taux}}</td>
        <td><a href="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
    </tr>
    {% endfor %}
    </tr>
    </tbody>
</table>
{% endif %}

المقطع [v-menu]


<!-- قائمة Bootstrap -->
<nav class="nav flex-column">
    <!-- عرض قائمة بالروابط HTML -->
    {% for optionMenu in modèle.optionsMenu %}
    <a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
    {% endfor %}
</nav>

36.6. الرد الجديد HTML

Image

في الإصدار السابق، كان رمز الرد HTML ينتهي بإعادة توجيه:


class HtmlResponse(InterfaceResponse):

    def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        # الاستجابة HTML تعتمد على رمز الحالة الذي يقدمه وحدة التحكم
        état = résultat["état"]

        

        # الآن يجب إنشاء URL لإعادة التوجيه دون نسيان الرمز المميز CSRF إذا طُلب
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # استجابة إعادة التوجيه
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

أصبح هذا الرمز الآن كما يلي:


def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        

        # الآن يجب إنشاء رمز إعادة التوجيه URL دون نسيان الرمز المميز CSRF إذا طُلب
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # استجابة إعادة التوجيه
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

36.7. الاختبارات

Image

لنضع البادئة التالية في ملف التكوين [parameters]:



        # رمز CSRF
        "with_csrftoken"True,
        # قواعد البيانات المدارة: MySQL (mysql)، PostgreSQL (pgres)
        "databases"["mysql""pgres"],
        # بادئة URL الخاصة بالتطبيق
        # تعيين السلسلة فارغة إذا لم تكن هناك حاجة إلى بادئة، أو /بادئة في الحالات الأخرى
        "prefix_url""/do",

لنشغل التطبيق ثم نطلب URL [http://localhost:5000/do]، والاستجابة هي كما يلي:

Image

  • في [1]، بادئة URL؛
  • في [2]، الرمز المميز CSRF؛

يمكن أيضًا اختبار التطبيق باستخدام اختبارات وحدة التحكم الخاصة بـ [http-clients/09]:

Image

في ملفات التكوين [1] و [2]، يجب تضمين البادئة الخاصة بـ URL في URL الخاص بالخادم:


        "server": {
            # "urlServer": "http://127.0.0.1:5000"،
            "urlServer""http://127.0.0.1:5000/do",
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                
            }
  • السطر 3: أصبح URL الخاص بالخادم يتضمن الآن البادئة الخاصة بـ URL؛

بعد إجراء هذا التعديل، يجب أن تعمل جميع اختبارات وحدة التحكم.