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:
    #  syspath configuration
    import syspath
    config['syspath'] = syspath.configure(config)

    #  application setup
    import parameters
    config['parameters'] = parameters.configure(config)

    #  database configuration
    import database
    config["database"] = database.configure(config)

    #  instantiation of application layers
    import layers
    config['layers'] = layers.configure(config)

    #  configuration MVC of the [web] layer
    config['mvc'] = {}

    #  web] layer controller configuration
    import controllers
    config['mvc'].update(controllers.configure(config))

    #  actions ASV (Action Show View)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    #  actions ADS (Action Do Something)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    #  response configuration HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    #  route configuration
    import routes
    routes.configure(config)

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

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

#  dependencies

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

#  application configuration
config = {}

#  the front controller
def front_controller() -> tuple:
    #  forward the request to the main controller
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

#  application root
def index() -> tuple:
    #  redirect to /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

#  init-session
def init_session(type_response: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  authenticate-user
def authentifier_utilisateur() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  calculate-tax
def calculer_impot() -> tuple:
    #  execute the controller associated with the action
    return front_controller()


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

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

#  dependencies

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

#  configuration
config = {}

#  the front controller
def front_controller() -> tuple:
    #  forward the request to the main controller
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

#  application root
def index() -> tuple:
    #  redirect to /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:
    #  execute the controller associated with the action
    return front_controller()

#  authenticate-user
def authentifier_utilisateur(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()



#  init-session-without-csrftoken for json and xml clients
def init_session_without_csrftoken(type_response: str) -> tuple:
    #  redirect to /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):
    #  route settings

    #  flask application
    app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
    config['app'] = app

    #  import routes from web application
    if config['parameters']['with_csrftoken']:
        import routes_with_csrftoken as routes
    else:
        import routes_without_csrftoken as routes

    #  inject configuration into routes
    routes.config = config

    #  the application's URL prefix
    prefix_url = config["parameters"]["prefix_url"]

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

    #  flask application routes
    #  application root
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

    #  init-session
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

    #  init-session-without-csrftoken
    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)

    #  authenticate-user
    app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
                     view_func=routes.authentifier_utilisateur)

    #  calculate-tax
    app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impot)

    #  batch tax calculation
    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)

    #  delete-simulation
    app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
                     view_func=routes.supprimer_simulation)

    #  end of session
    app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
                     view_func=routes.fin_session)

    #  display-calculation-tax
    app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_calcul_impot)

    #  get-admindata
    app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
                     view_func=routes.get_admindata)

    #  afficher-vue-calcul-impot
    app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_calcul_impot)

    #  display-view-authentication
    app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_authentification)

    #  display-view-list-simulations
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_simulations)

    #  display-view-error_list
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_erreurs)

    #  special case
    if with_csrftoken:
        #  init-session-without-csrftoken for json and xml clients
        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

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

1
2
3
4
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  path elements are retrieved
        params = request.path.split('/')
        action = params[1]

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

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

1
2
3
4
5
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  path elements are retrieved
        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:
        #  we encapsulate the paged data in the model
        modèle = {}
        #  application status
        état = résultat["état"]
        #  the model depends on the state
        

        #  csrf token
        modèle['csrf_token'] = super().get_csrftoken(config)

        #  possible actions from the view
        modèle['actions_possibles'] = []

        #  we render the model
        return modèle

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

def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        #  we encapsulate the paged data in the model
        modèle = {}
        #  application status
        état = résultat["état"]
        #  the model depends on the state
        

        #  csrf token
        modèle['csrf_token'] = super().get_csrftoken(config)

        #  possible actions from the view
        modèle['actions_possibles'] = []

        #  URL prefix
        modèle["prefix_url"] = config["parameters"]["prefix_url"]

        #  we render the model
        return modèle

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

Image

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

جزء [v-authentication]


<!-- form HTML - post its values with the [authenticate-user] action -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
 
    <!-- title -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>

 
</form>

جزء [v-calcul-impot]


<!-- form HTML posted -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
    <!-- 12-column message on blue background -->
    
 
</form>

مقتطف [v-liste-simulations]



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

 
<!-- simulation table -->
<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 menu -->
<nav class="nav flex-column">
    <!-- display a list of links 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):
        #  the HTML response depends on the status code returned by the controller
        état = résultat["état"]

        

        #  now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        #  redirect response
        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):
        

        #  now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        #  redirect response
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

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

Image

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



        # token csrf
        "with_csrftoken"True,
        # bases gérées MySQL (mysql), PostgreSQL (pgres)
        "databases"["mysql""pgres"],
        # préfixe des URL de l'application
        # mettre la chaîne vide si on ne veut pas de préfixe ou /préfixe sinon
        "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؛

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