Skip to content

35. تمرين تطبيقي: الإصدار 15

35.1. مقدمة

يهدف هذا الإصدار إلى حل المشكلات المتعلقة بتحديث صفحات التطبيق في المتصفح (F5). لنأخذ مثالاً. قام المستخدم للتو بحذف المحاكاة التي تحمل الرقم التعريفي id=3:

Image

بعد الحذف، يكون عنوان URL في المتصفح هو [/delete-simulation/3/…]. إذا قام المستخدم بتحديث الصفحة (F5)، يتم إعادة تشغيل عنوان URL [1]. لذلك نطلب حذف المحاكاة التي تحمل الرقم التعريفي id=3 مرة أخرى. والنتيجة هي كما يلي:

Image

إليك مثال آخر. قام المستخدم للتو بحساب ضريبة:

  • في [1]، عنوان URL [/calculate-tax] الذي تم الاستعلام عنه للتو باستخدام طلب POST؛ Image

إذا قام المستخدم بتحديث الصفحة (F5)، فسيتلقى رسالة تحذير:

Image

تؤدي عملية تحديث الصفحة إلى إعادة تنفيذ آخر طلب قام به المتصفح، وهو في هذه الحالة [POST /calculate-tax]. عند طلب إعادة تنفيذ طلب POST، تعرض المتصفحات تحذيرًا مشابهًا للتحذير أعلاه. يحذر هذا من إعادة تنفيذ إجراء تم تنفيذه بالفعل. لنفترض أن طلب POST هذا أكمل عملية شراء؛ سيكون من المؤسف تكراره.

بالإضافة إلى ذلك، سنقيد قدرة المستخدم على كتابة عناوين URL في متصفحه. لنأخذ إحدى طرق العرض السابقة كمثال:

Image

الروابط التي توفرها هذه الصفحة هي:

  • [قائمة المحاكاة] المرتبطة بعنوان URL [/lister-simulations]؛
  • [إنهاء الجلسة] المرتبطة بعنوان URL [/end-session]؛
  • [إرسال] المرتبطة بعنوان URL (غير موضح أعلاه) [/calculate-tax]؛

عند عرض شاشة حساب الضريبة، لن نقبل سوى الإجراءات [/list-simulations، /end-session، /calculate-tax]. إذا أدخل المستخدم إجراءً آخر في متصفحه، فسيتم إظهار رسالة خطأ. سنقوم بإجراء هذا النوع من التحقق من الصحة لجميع الشاشات الأربع للتطبيق.

نقترح حل مشكلة تحديث الصفحة على النحو التالي:

  • سنميز بين نوعين من الإجراءات:
    • إجراءات ADS (Action Do Something) التي تغير حالة التطبيق. عادةً ما تحتوي إجراءات ADS على معلمات في عنوان URL أو نص الطلب؛
    • إجراءات ASV (Action Show View) التي تعرض طريقة عرض دون تغيير حالة التطبيق. سيكون هناك عدد من إجراءات ASV يساوي عدد طرق العرض V. لا تحتوي إجراءات ASV على معلمات؛
  • حتى الآن، كانت إجراءات ADS تُنفَّذ ثم تُستكمل بعرض طريقة العرض V بعد إعداد نموذجها M. ومن الآن فصاعدًا، ستقوم هذه الإجراءات بوضع نموذج طريقة العرض M في الجلسة وتوجيه المتصفح لإعادة التوجيه إلى إجراء ASV المسؤول عن عرض طريقة العرض V؛
  • لن يتم عرض طرق العرض V إلا بعد إجراء ASV. وستسترد نموذجها من الجلسة؛

ميزة هذه الطريقة هي أن المتصفح سيعرض عنوان URL الخاص بـ ASV في شريط العناوين. سيؤدي تحديث الصفحة إلى إعادة تنفيذ إجراء ASV. لا يغير هذا الإجراء حالة التطبيق ويستخدم نموذج الجلسة . لذلك، سيتم إعادة عرض نفس الصفحة دون أي آثار جانبية. أخيرًا، وبسبب عمليات إعادة التوجيه، لن يرى المستخدم سوى عناوين URL لإجراءات ASV في متصفحه وسيشعر وكأنه يتنقل من صفحة إلى أخرى؛

35.2. التنفيذ

Image

يتم إنشاء الدليل [impots/http-servers/10] مبدئيًا عن طريق نسخ الدليل [impots/http-servers/09]. ثم يتم تعديله.

35.2.1. المسارات الجديدة

Image

في كل من ملفات [routes_with_csrftoken] و [routes_without_csrftoken]، نحتاج إلى إنشاء المسارات الأربعة لأعمال ASV الأربعة التي تعرض العروض الأربعة. وتبقى المسارات الأخرى دون تغيير.

في [routes_with_csrftoken]:

#  afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-authentication
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-list-simulations
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-error_list
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

الأسطر 1–23: أنشأنا أربعة مسارات لأربعة إجراءات ASV:

  • [/display-authentication-view]، السطر 8، يعرض عرض المصادقة؛
  • [/display-tax-calculation-view]، السطر 2، يعرض عرض حساب الضريبة؛
  • [/display-simulation-list-view]، السطر 14، يعرض عرض المحاكاة؛
  • [/display-error-list-view]، السطر 20، يعرض عرض الأخطاء غير المتوقعة؛

نقوم بنفس الشيء في ملف [routes_with_csrftoken] للمسارات التي لا تحتوي على رمز:

#  routes ASV -------------------------
#  afficher-vue-calcul-impot
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-authentication
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-list-simulations
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-view-error_list
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

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

Image

تقوم [AuthenticationViewController] بتنفيذ إجراء ASV [/display-authentication-view]:

from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueAuthentificationController(InterfaceController):

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

        #  change of view - just a status code to set
        return {"action": action, "état": 1100, "réponse": ""}, status.HTTP_200_OK

ذكرنا أن إجراءات ASV لا تحتوي على معلمات ولا تغير حالة التطبيق. نحن نعرض ببساطة العرض المطلوب عن طريق تعيين رمز حالة في السطر 14.

يقوم وحدة التحكم [AfficherVueCalculImpotController] بتنفيذ إجراء ASV [/afficher-vue-calcul-impot]:

from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueCalculImpotController(InterfaceController):

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

        #  view change - just a status code to set
        return {"action": action, "état": 1400, "réponse": ""}, status.HTTP_200_OK

يقوم وحدة التحكم [AfficherVueListeSimulationsController] بتنفيذ إجراء ASV [/afficher-vue-liste-simulations]:

from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueListeSimulationsController(InterfaceController):

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

        #  view change - just a status code to set
        return {"action": action, "état": 1200, "réponse": ""}, status.HTTP_200_OK

يقوم وحدة التحكم [AfficherVueListeErreursController] بتنفيذ إجراء ASV [/afficher-vue-liste-erreurs]:

from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController

class AfficherVueListeErreursController(InterfaceController):

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

        #  view change - just a status code to set
        return {"action": action, "état": 1300, "réponse": ""}, status.HTTP_200_OK

دعونا نلخص رموز الحالة:

  • يجب أن يعرض الرمز 1100 شاشة المصادقة؛
  • يجب أن يعرض الرمز 1400 عرض حساب الضريبة؛
  • يجب أن يعرض الرمز 1200 عرض المحاكاة؛
  • يجب أن يعرض الرمز 1300 عرض الأخطاء غير المتوقعة؛

35.2.3. تكوين MVC الجديد

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

  • [controllers]: قائمة وحدات التحكم C لتطبيق MVC؛
  • [ads_actions]: قائمة إجراءات ADS (Action Do Something
  • [asv_actions]: قائمة بإجراءات ASV (Action Show View
  • [responses]: قائمة بفئات استجابات HTTP للتطبيق؛

لنبدأ بالأبسط، ملف استجابات HTTP [responses]:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  answers HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse

    #  different response types (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }

    #  return response dictionary HTTP
    return {
        #  answers HTTP
        "responses": responses,
    }

لا مفاجآت هنا.

ملف [controllers] ليس مفاجئًا أيضًا. لقد أضفنا ببساطة وحدات التحكم الجديدة لإجراءات ASV.

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  the main controller
    from MainController import MainController

    #  action controllers ADS
    from AuthentifierUtilisateurController import AuthentifierUtilisateurController
    from CalculerImpotController import CalculerImpotController
    from CalculerImpotsController import CalculerImpotsController
    from FinSessionController import FinSessionController
    from GetAdminDataController import GetAdminDataController
    from InitSessionController import InitSessionController
    from ListerSimulationsController import ListerSimulationsController
    from SupprimerSimulationController import SupprimerSimulationController
    from AfficherCalculImpotController import AfficherCalculImpotController

    #  action controllers ASV
    from AfficherVueCalculImpotController import AfficherVueCalculImpotController
    from AfficherVueAuthentificationController import AfficherVueAuthentificationController
    from AfficherVueListeErreursController import AfficherVueListeErreursController
    from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController

    #  authorized shares and their controllers
    controllers = {
        #  initialization of a calculation session
        "init-session": InitSessionController(),
        #  user authentication
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        #  link to tax calculation view
        "afficher-calcul-impot": AfficherCalculImpotController(),
        #  tax calculation in individual mode
        "calculer-impot": CalculerImpotController(),
        #  batch mode tax calculation
        "calculer-impots": CalculerImpotsController(),
        #  list of simulations
        "lister-simulations": ListerSimulationsController(),
        #  deleting a simulation
        "supprimer-simulation": SupprimerSimulationController(),
        #  end of calculation session
        "fin-session": FinSessionController(),
        #  obtaining data from tax authorities
        "get-admindata": GetAdminDataController(),
        #  main controller
        "main-controller": MainController(),
        #  display authentication view
        "afficher-vue-authentification": AfficherVueAuthentificationController(),
        #  display tax calculation view
        "afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
        #  displaying the simulation view
        "afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
        #  displaying the error view
        "afficher-vue-liste-erreurs": AfficherVueListeErreursController()
    }

    #  make the controller configuration
    return {
        #  controllers
        "controllers": controllers,
    }

تكوين [asv_actions] لإجراءات ASV هو كما يلي:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  HTML views and their models depend on the state rendered by the controller
    #  actions ASV (Action Show view)
    asv = [
        {
            #  authentication view
            "états": [
                1100,  #  /show-view-authentication
            ],
            "view_name": "views/vue-authentification.html",
        },
        {
            #  tax calculation
            "états": [
                1400,  #  /display-view-tax-calculation
            ],
            "view_name": "views/vue-calcul-impot.html",
        },
        {
            #  view of simulation list
            "états": [
                1200,  #  /display-view-list-simulations
            ],
            "view_name": "views/vue-liste-simulations.html",
        },
        {
            #  view of error list
            "états": [
                1300,  #  /display-view-error-list
            ],
            "view_name": "views/vue-erreurs.html",
        },
    ]

    #  return the ASV configuration
    return {
        #  views and models
        "asv": asv,
    }
  • يحتوي ملف [asv_actions] على أربعة إجراءات جديدة، والتي تم تلخيص وظائفها أدناه:
    • لا تحتوي على معلمات؛
    • تعرض طريقة عرض محددة يوجد قالبها في الجلسة؛
  • تربط قائمة [asv] في الأسطر 6–35 عرضًا بكل إجراء من إجراءات ASV؛

يحتوي ملف [ads_actions] على إجراءات ADS:

def configure(config: dict) -> dict:
    #  application configuration MVC

    #  view models
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    #  actions ADS (Action Do Something)
    ads = [
        {
            "états": [
                400,  #  /end-session success
            ],
            #  redirection to action ADS
            "to": "/init-session/html",
        },
        {
            "états": [
                700,  #  /init-session - success
                201,  #  /authentifier-user failure
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-authentification",
            #  model of the following view
            "model_for_view": ModelForAuthentificationView()
        },
        {
            "états": [
                200,  #  /authentifier-user success
                300,  #  /calculate-tax-success
                301,  #  /calculate-tax failure
                800,  #  /display-tax-calculation link
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-calcul-impot",
            #  model of the following view
            "model_for_view": ModelForCalculImpotView()
        },
        {
            "états": [
                500,  #  /lister-simulations success
                600,  #  /suppress-simulation success
            ],
            #  redirection to action ASV
            "to": "/afficher-vue-liste-simulations",
            #  model of the following view
            "model_for_view": ModelForListeSimulationsView()
        },
    ]

    #  view of unexpected errors
    view_erreurs = {
        #  redirection to action ASV
        "to": "/afficher-vue-liste-erreurs",
        #  model of the following view
        "model_for_view": ModelForErreursView()
    }

    #  return the MVC configuration
    return {
        #  shares ADS
        "ads": ads,
        #  the sight of unexpected errors
        "view_erreurs": view_erreurs,
    }
  • الأسطر 11–51: قائمة إجراءات ADS (Action Do Something). تم تضمين جميع الإجراءات من الإصدارات السابقة. ومع ذلك، فقد تغير سلوكها:
    • فهي لا تعرض طريقة العرض V. بل تقوم فقط بإعداد النموذج M لطريقة العرض V هذه؛
    • تطلب عرض العرض V عبر إعادة توجيه إلى إجراء ASV المرتبط بالعرض V؛
  • لا تؤدي جميع إجراءات ADS إلى إعادة توجيه إلى إجراء ASV: الأسطر 12–18، تؤدي إجراء ADS [/fin-session] إلى إعادة توجيه إلى إجراء ADS [/init-session/html]. للتمييز بين عمليات إعادة التوجيه ADS -> ADS و ADS -> ASV، يمكننا استخدام القالب [model_for_view]. هذا غير موجود لعمليات إعادة التوجيه ADS -> ADS؛

يتغير ملف [main/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))

    #  we return the configuration
    return config

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

Image

ستقوم النماذج بإنشاء معلومات جديدة. لنأخذ، على سبيل المثال، نموذج عرض المصادقة:

from flask import Request
from werkzeug.local import LocalProxy

from AbstractBaseModelForView import AbstractBaseModelForView

class ModelForAuthentificationView(AbstractBaseModelForView):

    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 = {}
        

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

        #  possible actions from the view
        modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]

        #  we render the model
        return modèle
  • توجد الميزة الجديدة في السطر 17: سيقوم كل نموذج بإنشاء قائمة بالإجراءات الممكنة عند عرض العرض V، الذي يمثل النموذج M الخاص به. لرؤية هذه الإجراءات، نحتاج إلى العودة إلى العرض V. في حالة عرض المصادقة:

Image

  • نرى أن عرض المصادقة يقدم إجراءً واحدًا فقط، وهو زر [Validate]. هذا الإجراء هو [/authenticate-user]؛

لقد كتبنا:


        # actions possibles à partir de la vue
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

في العرض أعلاه، نرى أنه إذا قام المستخدم بتحديث العرض، فسيتم إعادة تشغيل الإجراء [1] [/display-authentication-view]. لذلك يجب أن يكون مصرحًا به. كان بإمكاننا اختيار عدم التصريح به، وفي هذه الحالة كان المستخدم سيواجه خطأً في كل مرة يقوم فيها بإعادة تحميل الصفحة. وقد قررنا أن هذا الأمر غير مرغوب فيه.

يتم وضع الإجراءات الممكنة في نموذج العرض. ونحن نعلم أن هذا النموذج سيتم تخزينه في الجلسة.

نقوم بذلك لكل عرض من العروض الأربعة. تكون الإجراءات الممكنة عندئذٍ كما يلي:

عرض المصادقة


modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]

عرض حساب الضرائب


# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-calcul-impot""calculer-impot""lister-simulations","fin-session"]

عرض قائمة المحاكاة


# possible actions from the view
actions_possibles = ["afficher-vue-liste-simulations""afficher-calcul-impot""fin-session"]
if len(modèle['simulations']) != 0:
  actions_possibles.append("supprimer-simulation")
modèle['actions_possibles'] = actions_possibles

عرض قائمة الأخطاء


# actions possibles à partir de la vue
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs""afficher-calcul-impot""lister-simulations", "fin-session"]

35.2.5. وحدة التحكم الرئيسية الجديدة

Image

تخضع [MainController] لبعض التغييرات:

#  import dependencies
import threading
import time
from random import randint

from flask_api import status
from flask_wtf.csrf import generate_csrf, validate_csrf
from werkzeug.local import LocalProxy
from wtforms import ValidationError

from InterfaceController import InterfaceController
from Logger import Logger
from SendAdminMail import SendAdminMail

def send_adminmail(config: dict, message: str):
    #  send an e-mail to the application administrator
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

#  main application controller
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  we process the request
        type_response1 = None
        logger = None
        try:
            #  path elements are retrieved
            params = request.path.split('/')

            #  action is the 1st element
            action = params[1]

            #  logger
            logger = Logger(config['parameters']['logsFilename'])

            

            #  the /display-view-error-list action is special
            #  we don't check anything, otherwise we risk entering an infinite loop of redirects
            erreur = False
            if action != "afficher-vue-liste-erreurs":
                #  if error, (result] is the result to be sent to the customer
                (erreur, résultat, type_response1) = MainController.check_action(params, session, config)

            #  if no error - action executed
            if not erreur:
                #  execute the controller associated with the action
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            #  (unexpected) exceptions
            résultat = {"action": action, "état": 131, "réponse": [f"{exception}"]}
            erreur = True

        finally:
            pass

        #  runtime error
        if erreur:
            #  erroneous request
            status_code = status.HTTP_400_BAD_REQUEST

        if config['parameters']['with_csrftoken']:
            #  add the csrf_token to the result
            résultat['csrf_token'] = generate_csrf()

        .

        #  we send the HTTP response
        return response, status_code

    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        

        #  result of the method
        return erreur, résultat, type_response1
  • الأسطر 39–44: يتم إجراء الفحوصات الأولى على الإجراء. سنعود إلى هذا لاحقًا. تُرجع الطريقة الثابتة [MainController.check_action] مجموعة مكونة من ثلاثة عناصر:
    • [error]: True إذا تم الكشف عن خطأ، False في الحالات الأخرى؛
    • [result]: نتيجة الخطأ إذا كان (error == TrueNone في الحالات الأخرى؛
    • [response_type1]: نوع (json، xml، html، None) الجلسة، كما هو موجود في الجلسة؛
  • السطر 39: لا يتم إجراء أي فحص إذا كان الإجراء هو إجراء ASV [display-error-list-viewالذي يعرض قائمة الأخطاء. في الواقع، إذا تم العثور على خطأ أثناء هذا الإجراء، فسيتم إعادة توجيهنا مرة أخرى إلى الإجراء [/display-error-list-view] وسندخل في حلقة إعادة توجيه لا نهائية؛
  • الطريقة الثابتة [check_action] هي كما يلي:
    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        #  retrieve the current action
        action = params[1]

        #  no errors and no results to start with
        erreur = False
        résultat = None

        #  session type must be known before certain actions ADS
        type_response1 = session.get('typeResponse')
        if type_response1 is None and action != "init-session":
            #  we note the error
            résultat = {"action": action, "état": 101,
                        "réponse": ["pas de session en cours. Commencer par action [init-session]"]}
            erreur = True

        #  some ADS actions require authentication
        user = session.get('user')
        if user is None and action not in ["init-session",
                                           "authentifier-utilisateur",
                                           "afficher-vue-authentification"]:
            #  we note the error
            résultat = {"action": action, "état": 101,
                        "réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
            erreur = True

        #  from a view, only certain actions are possible
        if not erreur and action != "init-session":
            #  for the moment no action possible
            actions_possibles = None
            #  retrieve the model of the future session view, if it exists
            modèle = session.get('modèle')
            #  if a model has been found, its possible actions are retrieved
            if modèle:
                actions_possibles = modèle.get('actions_possibles')
            #  if you have a list of possible actions, check that the current action is one of them
            if actions_possibles and action not in actions_possibles:
                #  we note the error
                résultat = {"action": action, "état": 151,
                            "réponse": [f"action [{action}] incorrecte dans l'environnement actuel"]}
                erreur = True


        #  mistake?
        if not erreur and config['parameters']['with_csrftoken']:
            #  check the validity of the csrf token
            #  the csrf_token is the last element of the path
            csrf_token = params.pop()
            try:
                #  an exception will be thrown if csrf_token is invalid
                validate_csrf(csrf_token)
            except ValidationError as exception:
                #  csrf token invalid
                résultat = {"action": action, "état": 121, "réponse": [f"{exception}"]}
                #  we note the error
                erreur = True

        #  result of the method
        return erreur, résultat, type_response1
  • السطر 2: تقوم طريقة [check_action] بإجراء عدة فحوصات حول صحة الإجراء الحالي؛
  • الأسطر 6–26، 45–57: كانت الإصدارات السابقة تجري هذه الفحوصات بالفعل؛
  • الأسطر 28–42: تمت إضافة فحص جديد. نتحقق مما إذا كان الإجراء الحالي ممكنًا في ضوء الحالة الحالية للتطبيق. إذا كان الإجراء الحالي غير ممكن، فإننا ننشئ رمز الحالة 151 (السطر 40)، مما يضمن إعادة توجيه الإجراء الحالي إلى عرض الأخطاء غير المتوقعة؛

35.2.6. استجابة HTML الجديدة

Image

تنطبق التغييرات الحالية على جلسات HTML فقط. لا تتأثر جلسات JSON أو XML. يتم تحديث فئة [HtmlResponse] على النحو التالي:

#  dependencies

from flask import make_response, redirect, render_template
from flask.wrappers import Response
from flask_api import status
from flask_wtf.csrf import generate_csrf
from werkzeug.local import LocalProxy

from InterfaceResponse import InterfaceResponse

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"]

        #  find out if the state was produced by a ASV action
        #  in which case a
        asv_configs = config['mvc']["asv"]
        trouvé = False
        i = 0
        #  browse the list of views
        nb_views = len(asv_configs)
        while not trouvé and i < nb_views:
            #  view n° i
            asv_config = asv_configs[i]
            #  states associated with view n° i
            états = asv_config["états"]
            #  is the state you're looking for in the states associated with view n° i?
            if état in états:
                trouvé = True
            else:
                #  next view
                i += 1

        #  found?
        if trouvé:
            #  this is a ASV action - you need to display a view whose model is already in session
            #  generate the HTML response code
            html = render_template(asv_config["view_name"], modèle=session['modèle'])
            #  build the HTTP response
            response = make_response(html)
            response.headers['Content-Type'] = 'text/html; charset=utf-8'
            #  we return the result
            return response, status_code

        #  not found - this is an action status code ADS
        #  this will be followed by a redirection
        redirected = False
        for ads in config['mvc']['ads']:
            #  conditions requiring redirection
            états = ads["états"]
            if état in états:
                #  redirection
                redirected = True
                break
        #  redirection dictionary for unexpected errors
        if not redirected:
            ads = config['mvc']['view_erreurs']

        #  is it a redirect to a ASD or ASV action?
        #  if there's a template, then it's a redirection to a ASV action
        #  then calculate the model of view V to be displayed by action ASV
        model_for_view = ads.get("model_for_view")
        if model_for_view:
            #  calculation of the next view model
            modèle = model_for_view.get_model_for_view(request, session, config, résultat)
            #  the model is sessioned for the following view
            session['modèle'] = modèle

            #  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
  • الأسطر 18–35: التحقق مما إذا كانت الحالة التي أعادها الإجراء الأخير الذي تم تنفيذه هي حالة إجراء ASV؛
  • الأسطر 36–46: إذا كان الأمر كذلك، يتم عرض عرض V المرتبط بإجراء ASV باستخدام النموذج الموجود في الجلسة المرتبطة بمفتاح [‘model’]؛
  • الأسطر 48-60: عندما نصل إلى هذه النقطة، نعلم أن الحالة الناتجة عن آخر إجراء تم تنفيذه هي حالة إجراء ADS. سيؤدي ذلك إلى تشغيل إعادة التوجيه. نبحث في ملف التكوين عن تعريفه؛
  • السطر 62: عندما نصل إلى هذه النقطة، يكون لدينا التكوين اللازم لإجراء إعادة التوجيه. هناك حالتان:
    • إعادة توجيه إلى إجراء ADS آخر. في هذه الحالة، لا يوجد نموذج عرض لحسابه؛
    • هي إعادة توجيه إلى إجراء ASV. في هذه الحالة، يجب حساب نموذج العرض (السطور 67-68). ثم يُضاف هذا النموذج إلى الجلسة (السطر 70)؛
  • الأسطر 72-76: يتم حساب عنوان URL لإعادة التوجيه؛
  • السطور 78-79: يتم إرسال استجابة إعادة التوجيه إلى العميل؛

35.3. الاختبارات

قم بإجراء الاختبارات التالية باستخدام متصفح:

  • استخدم التطبيق كالمعتاد. تحقق من أن عناوين URL الوحيدة التي يعرضها المتصفح هي عناوين URL ASV [/display-view-view_name]؛
  • قم بتحديث الصفحات (F5) وتأكد من عرض نفس الصفحة مرة أخرى. لا توجد أي آثار جانبية؛

بالإضافة إلى ذلك، استخدم العميل [impots/http-clients/09]. نظرًا لأن التغييرات التي تم إجراؤها تؤثر فقط على جلسات HTML، يجب أن يستمر العملاء [main، main2، main3، Test1HttpClientDaoWithSession، Test2HttpClientDaoWithSession] في العمل.

الآن دعونا نلقي نظرة على حالة يكون فيها الإجراء مستحيلًا. يتم عرض العرض التالي:

Image

بدلاً من [1]، أدخل عنوان URL [/delete-simulation/1]. الإجراء [/delete-simulation] ليس من بين الإجراءات التي يقدمها العرض، وهي الإجراءات من 1 إلى 4. لذلك سيتم رفضه. استجابة الخادم هي كما يلي:

Image