Skip to content

35. تمرین عملی: نسخه ۱۵

35.1. مقدمه

این نسخه با هدف رفع مشکلات مربوط به تازه‌سازی صفحات برنامه در مرورگر (F5) ارائه شده است. بیایید یک مثال بزنیم. کاربر به تازگی شبیه‌سازی با شناسه=3 را حذف کرده است:

Image

پس از حذف، URL در مرورگر به [/supprimer-simulation/3/…] تبدیل می‌شود. اگر کاربر صفحه را رفرش کند (F5)، شبیه‌سازی URL و [1] مجدداً اجرا می‌شود. بنابراین، درخواست حذف شبیه‌سازی با id=3 دوباره ارسال می‌شود. نتیجه به شرح زیر است:

Image

در اینجا یک مثال دیگر آورده شده است. کاربر به تازگی مالیاتی را محاسبه کرده است:

  • در [1]، URL و [/calculer-impot] که به تازگی با یک POST پرس‌وجو شده‌اند؛

اگر کاربر صفحه را تازه‌سازی کند (F5)، پیغام هشدار دریافت می‌کند:

Image

عملیات تازه‌سازی صفحه، آخرین درخواست ارسال‌شده توسط مرورگر را مجدداً اجرا می‌کند، که در این مورد یک [POST /calculer-impot] است. وقتی از کاربر خواسته می‌شود یک POST را مجدداً اجرا کند، مرورگرها هشداری مشابه مورد بالا نمایش می‌دهند. این هشدار می‌دهد که در حال اجرای عملی است که قبلاً انجام شده است. فرض کنید این POST یک خرید را تکمیل کرده باشد؛ تکرار آن نامطلوب خواهد بود.

علاوه بر این، ما توانایی کاربر برای تایپ URL در مرورگرش را محدود خواهیم کرد. بیایید یکی از نماهای قبلی را به‌عنوان مثال در نظر بگیریم:

Image

لینک‌های ارائه‌شده در این صفحه عبارتند از:

  • [Liste des simulations] مرتبط با URL و [/lister-simulations]؛
  • [Fin de session] مرتبط با URL و [/fin-session]؛
  • [Valider] مرتبط با URL (در بالا نشان داده نشده) [/calculer-impot];

وقتی نمای محاسبه مالیات نمایش داده می‌شود، ما تنها اقدام [/lister-simulations, /fin-session, /calculer-impot] را می‌پذیریم. اگر کاربر در مرورگر خود اقدامی متفاوت وارد کند، خطایی نمایش داده خواهد شد. ما این نوع بررسی را برای هر چهار نمای برنامه انجام خواهیم داد.

ما پیشنهاد می‌کنیم مسئلهٔ تازه‌سازی صفحات را به شرح زیر حل کنیم:

  • ما بین دو نوع اقدام تمایز قائل خواهیم شد:
    • اقدامات ADS (اقدامات انجام کاری) که وضعیت برنامه را تغییر می‌دهند. اقدامات ADS معمولاً دارای پارامترهایی در URL یا در بدنه درخواست هستند؛
    • اقدامات ASV (اقدام نمایش نما) یک نما را بدون تغییر وضعیت برنامه نمایش می‌دهند. به ازای هر نمای V، به همان تعداد اقدام ASV وجود خواهد داشت. اقدامات ASV هیچ پارامتری ندارند؛
  • تا کنون، عملیات ADS اجرا می‌شد و سپس با نمایش یک نما V پس از آماده‌سازی مدل M آن، متوقف می‌شد. از این پس، آنها قالب M نما را در جلسه قرار می‌دهند و به مرورگر دستور می‌دهند که به اقدام ASV که مسئول نمایش نما V است، هدایت (redirect) کند؛
  • ویو V تنها پس از تکمیل یک اکشن ASV نمایش داده می‌شود. آنها قالب خود را از جلسه (session) بازیابی می‌کنند؛

مزیت این روش این است که مرورگر 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]:


# نمایش-محاسبه-مالیات-ویو
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
    #اجرای کنترلر مرتبط با اکشن
    return front_controller()

#نمایش-نما-احراز-هویت
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
    # اجرای کنترلر مرتبط با اکشن
    return front_controller()

#نمایش-لیست-نما-شبیه‌سازی
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
    #کنترلر مرتبط با اقدام اجرا می‌شود
    return front_controller()

#نمایش-فهرست-نمایش-خطا
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

خطوط ۱–۲۳: ما چهار مسیر برای چهار اقدام ASV ایجاد کرده‌ایم:

  • [/afficher-vue-authentification]، خط ۸، نمای احراز هویت را نمایش می‌دهد؛
  • [/afficher-vue-calcul-impot]، خط ۲، نمای محاسبه مالیات را نمایش می‌دهد؛
  • [/afficher-vue-liste-simulations]، خط 14، نمای شبیه‌سازی را نمایش می‌دهد؛
  • [/afficher-vue-liste-erreurs]، خط ۲۰، نمای خطاهای غیرمنتظره را نمایش می‌دهد؛

همین امر برای فایل [routes_with_csrftoken] برای مسیرهایی که توکن ندارند نیز صدق می‌کند:


#مسیرها ASV -------------------------
# نمایش-محاسبه-مالیات
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
    #کنترلر مرتبط با اکشن اجرا می‌شود
    return front_controller()

#نمایش-ویوی-احراز-هویت
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

#نمایش-فهرست-نما-شبیه‌سازی
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
    #کنترلر مرتبط با اقدام اجرا می‌شود
    return front_controller()

#نمایش-فهرست-نمایش-خطا
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

35.2.2. کنترل‌کننده‌های جدید

Image

کنترل‌کننده [AfficherVueAuthentificationController] اقدام ASV [/afficher-vue-authentification] را اجرا می‌کند:


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):
        # بازیابی عناصر مسیر
        params = request.path.split('/')
        action = params[1]

        #تغییر نما – فقط یک کد وضعیت برای تنظیم
        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):
        # بازیابی عناصر مسیر
        params = request.path.split('/')
        action = params[1]

        #تغییر نما – فقط یک کد وضعیت برای تنظیم
        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):
        #بازیابی عناصر مسیر
        params = request.path.split('/')
        action = params[1]

        #تغییر نما – فقط یک کد وضعیت برای تنظیم
        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):
        #بازیابی عناصر مسیر
        params = request.path.split('/')
        action = params[1]

        #تغییر نما – فقط یک کد وضعیت برای تنظیم
        return {"action": action, "état"1300"réponse"""}, status.HTTP_200_OK

بیایید کدهای وضعیت را خلاصه کنیم:

  • کد 1100 باید نمای احراز هویت را نمایش دهد؛
  • کد ۱۴۰۰ باید نمای محاسبه مالیات را نمایش دهد؛
  • کد 1200 باید نمای شبیه‌سازی را نمایش دهد؛
  • کد 1300 باید نمای خطاهای غیرمنتظره را نمایش دهد؛

35.2.3. پیکربندی جدید MVC

Image

از آنجا که بیش از حد بزرگ شده بود، پیکربندی MVC به چهار فایل تقسیم شده است:

  • [controllers]: فهرست کنترل‌کننده‌های C برای برنامه MVC؛
  • [ads_actions]: فهرست اقدامات در ADS (اقدام انجام کاری) را فهرست می‌کند؛
  • [asv_actions]: فهرست اقدامات در ASV (اقدام نمایش نما) را فهرست می‌کند؛
  • [responses]: کلاس‌های پاسخ HTTP را برای برنامه فهرست می‌کند؛

بیایید با ساده‌ترین مورد شروع کنیم، فایل پاسخ HTTP [responses]:


def configure(config: dict) -> dict:
    #پیکربندی برنامه MVC

    # پاسخ‌ها HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse

    #انواع مختلف پاسخ (json، xml، html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }

    #تولید واژه‌نامه پاسخ HTTP
    return {
        #پاسخ‌های HTTP
        "responses": responses,
    }

شگفتی‌ای در کار نیست.

فایل کنترل‌کننده‌ها [controllers] نیز همان‌طور که انتظار می‌رفت است. کنترل‌کننده‌های جدید از عملیات ASV به‌سادگی به آن اضافه شده‌اند.


def configure(config: dict) -> dict:
    #پیکربندی برنامه MVC

    #کنترل‌کننده اصلی
    from MainController import MainController

    #کنترل‌کننده‌های عملی 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

    #کنترل‌کننده‌های اقدام ASV
    from AfficherVueCalculImpotController import AfficherVueCalculImpotController
    from AfficherVueAuthentificationController import AfficherVueAuthentificationController
    from AfficherVueListeErreursController import AfficherVueListeErreursController
    from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController

    # اقدامات مجاز و کنترل‌کننده‌های آن‌ها
    controllers = {
        # ابتدای یک جلسه محاسباتی
        "init-session": InitSessionController(),
        #احراز هویت کاربر
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # پیوند به نمای محاسبه مالیات
        "afficher-calcul-impot": AfficherCalculImpotController(),
        # محاسبه مالیات در حالت فردی
        "calculer-impot": CalculerImpotController(),
        # محاسبه مالیات به صورت دسته‌ای
        "calculer-impots": CalculerImpotsController(),
        # فهرست شبیه‌سازی‌ها
        "lister-simulations": ListerSimulationsController(),
        # حذف یک شبیه‌سازی
        "supprimer-simulation": SupprimerSimulationController(),
        #پایان جلسه محاسبه
        "fin-session": FinSessionController(),
        #بازیابی داده‌ها از مراجع مالیاتی
        "get-admindata": GetAdminDataController(),
        # کنترل‌کننده اصلی
        "main-controller": MainController(),
        # نمایش نمای احراز هویت
        "afficher-vue-authentification": AfficherVueAuthentificationController(),
        # نمایش نمای محاسبه مالیات
        "afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
        # نمای شبیه‌سازی
        "afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
        #نمایش نمای خطاها
        "afficher-vue-liste-erreurs": AfficherVueListeErreursController()
    }

    # پیکربندی اعتبارسنج‌ها
    return {
        # کنترل‌کننده‌ها
        "controllers": controllers,
    }

پیکربندی [asv_actions] برای اقدامات ASV به شرح زیر است:


def configure(config: dict) -> dict:
    #پیکربندی برنامه MVC

    # ویوهایی که HTML هستند و قالب‌های آن‌ها به حالتی که توسط کنترلر بازگردانده می‌شود بستگی دارند
    # ASV اقدامات (اقدام نمایش نما)
    asv = [
        {
            # نما نمایش احراز هویت
            "états": [
                1100,  #/نمایش-ویوی-احراز-هویت
            ],
            "view_name""views/vue-authentification.html",
        },
        {
            #نمای محاسبه مالیات
            "états"[
                1400,  # /نمایش-چشم‌انداز-محاسبه-مالیات
            ],
            "view_name""views/vue-calcul-impot.html",
        },
        {
            # نمایش فهرست شبیه‌سازی
            "états"[
                1200,  # /نمایش-لیست-شبیه‌سازی
            ],
            "view_name""views/vue-liste-simulations.html",
        },
        {
            # نمایش فهرست خطاها
            "états"[
                1300,  # /نمایش-فهرست-خطا-نما
            ],
            "view_name""views/vue-erreurs.html",
        },
    ]

    #پیکربندی رندر می‌شود ASV
    return {
        # نماها و قالب‌ها
        "asv": asv,
    }
  • فایل [asv_actions] شامل چهار اقدام جدید است که عملکرد آن‌ها در زیر خلاصه شده است:
    • آنها هیچ پارامتری ندارند؛
    • آنها یک نمای مشخص را نمایش می‌دهند که قالب آن در جلسه قرار دارد؛
  • فایل [asv]، خطوط ۶–۳۵، یک نما را با هر اقدام ASV مرتبط می‌سازد؛

فایل [ads_actions] شامل اقدامات ADS است:


def configure(config: dict) -> dict:
    #MVC پیکربندی برنامه

    #قالب‌های نما
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # ADS اقدامات (اقدام انجام دادن)
    ads = [
        {
            "états": [
                400,  # /end-session موفق
            ],
            # ارسال مجدد به اقدام ADS
            "to""/init-session/html",
        },
        {
            "états"[
                700,  # /init-session – موفقیت‌آمیز
                201,  # /احراز-هویت-کاربر – ناموفق
            ],
            # ارسال مجدد به اقدام ASV
            "to""/afficher-vue-authentification",
            #قالب برای نمای بعدی
            "model_for_view": ModelForAuthentificationView()
        },
        {
            "états"[
                200,  #/احراز-هویت-کاربر موفقیت
                300,  #/محاسبه-مالیات موفقیت‌آمیز
                301,  # /محاسبه-مالیات خطا
                800,  # /لینک نمایش محاسبه مالیات
            ],
            # ارسال مجدد به اقدام ASV
            "to""/afficher-vue-calcul-impot",
            #قالب برای نمای بعدی
            "model_for_view": ModelForCalculImpotView()
        },
        {
            "états"[
                500,  # /list-simulations موفقیت
                600,  # /حذف-شبیه‌سازی (موفق)
            ],
            # ارسال مجدد به اقدام ASV
            "to""/afficher-vue-liste-simulations",
            #قالب برای نمای بعدی
            "model_for_view": ModelForListeSimulationsView()
        },
    ]

    # نمايش خطاهای غيرمنتظره
    view_erreurs = {
        # ارسال مجدد به اقدام ASV
        "to""/afficher-vue-liste-erreurs",
        #قالب برای نمای زیر
        "model_for_view": ModelForErreursView()
    }

    #پیکربندی رندر می‌شود MVC
    return {
        # اقدامات ADS
        "ads": ads,
        #نمای خطاهای غیرمنتظره
        "view_erreurs": view_erreurs,
    }
  • خطوط ۱۱–۵۱: فهرست اقدامات ADS (اقدام انجام کاری). تمام اقدامات نسخه‌های قبلی موجود هستند. با این حال، رفتار آن‌ها تغییر کرده است:
    • آنها نمای V را نمایش نمی‌دهند. آنها صرفاً مدل M را برای آن نمای V آماده می‌کنند؛
    • آنها نمایش نما V را از طریق هدایت به اقدام ASV که با نما V مرتبط است، درخواست می‌کنند؛
  • همهٔ عملیات ADS به یک عملیات ASV هدایت نمی‌شوند: خطوط ۱۲–۱۸: عمل ADS [/fin-session] منجر به یک تغییر مسیر به عمل ADS [/init-session/html] می‌شود. برای تمایز بین هدایت‌های ADS → ADS و ADS → ASV، می‌توان از قالب [model_for_view] استفاده کرد. این برای تغییرمسیرهای ADS → ADS وجود ندارد؛

فایل [main/config] که شامل تمام پیکربندی‌ها است، به شرح زیر تغییر می‌کند:


def configure(config: dict) -> dict:
    #پیکربندی syspath
    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
    config['mvc'] = {}

    #پیکربندی کنترل‌کننده‌های لایه [web]
    import controllers
    config['mvc'].update(controllers.configure(config))

    # ASV اقدامات (اقدام نمایش نما)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    # ADS اکشن‌ها (اکشن انجام کاری)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    #پیکربندی پاسخ HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    #پیکربندی رندر می‌شود
    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:
        # فشرده‌سازی داده‌های صفحه در یک قالب
        modèle = {}
        

        #توکن CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # اقدامات ممکن از دیدگاه
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

        # قالب را رندر کنید
        return modèle
  • ویژگی جدید در خط ۱۷ قرار دارد: هر مدل زمانی که نمای V که مدل آن M است نمایش داده می‌شود، فهرستی از اقدامات ممکن تولید می‌کند. برای اینکه بفهمید این اقدامات چه هستند، باید به نمای V بازگردید. در مورد نمای احراز هویت:

Image

  • می‌توانیم ببینیم که نمای احراز هویت تنها یک اقدام را ارائه می‌دهد، یعنی اقدام مربوط به دکمه [Valider]. این اقدام [/authentifier-utilisateur] است؛

ما نوشته‌ایم:


        # اقدامات ممکن از دیدگاه
        modèle['actions_possibles'] = ["afficher-vue-authentification""authentifier-utilisateur"]

در نمای بالا، می‌توانیم ببینیم که اگر کاربر نما را تازه‌سازی کند، اقدام [1] [/afficher-vue-authentification] مجدداً اجرا خواهد شد. بنابراین باید مجاز اعلام شود. می‌توانستیم انتخاب کنیم که آن را مجاز نکنیم، در این صورت کاربر هر بار که صفحه را دوباره بارگذاری می‌کرد با یک خطا مواجه می‌شد. ما احساس کردیم که این امر مطلوب نیست.

اقدامات ممکن در قالب نما قرار می‌گیرند. می‌دانیم که این قالب در جلسه ذخیره خواهد شد.

ما این کار را برای هر یک از چهار نما انجام می‌دهیم. سپس اقدامات ممکن به شرح زیر است:

نمایه احراز هویت


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

نما نمایش محاسبه مالیات


# اقدامات ممکن از دیدگاه
modèle['actions_possibles'] = ["afficher-vue-calcul-impot""calculer-impot""lister-simulations","fin-session"]

نمای فهرست شبیه‌سازی


# اقدامات موجود در نما
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

نمایش لیست خطاها


# اقدامات موجود در نما
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs""afficher-calcul-impot""lister-simulations", "fin-session"]

35.2.5. کنترل‌کننده اصلی جدید

Image

کنترل‌کننده [MainController] در حال دستخوش تغییراتی است:


# وارد کردن وابستگی‌ها
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):
    # یک ایمیل برای مدیر برنامه ارسال می‌شود
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

#کنترل‌کننده اصلی برنامه
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #درخواست در حال پردازش است
        type_response1 = None
        logger = None
        try:
            # بازیابی عناصر مسیر
            params = request.path.split('/')

            # عمل اولین عنصر است
            action = params[1]

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

            

            # عمل /display-error-list-view خاص است
            # هیچ بررسی‌ای انجام نمی‌شود، در غیر این صورت خطر ورود به یک حلقه بی‌نهایت از هدایت‌ها وجود دارد
            erreur = False
            if action != "afficher-vue-liste-erreurs":
                # اگر خطایی رخ دهد، [result] نتیجه‌ای است که باید به کلاینت ارسال شود
                (erreur, résultat, type_response1) = MainController.check_action(params, session, config)

            # اگر خطایی وجود نداشته باشد، اکشن اجرا می‌شود
            if not erreur:
                #کنترلر مرتبط با اکشن اجرا می‌شود
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            # استثناها (غیرمنتظره)
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            erreur = True

        finally:
            pass

        #خطای زمان اجرا
        if erreur:
            # درخواست نامعتبر
            status_code = status.HTTP_400_BAD_REQUEST

        if config['parameters']['with_csrftoken']:
            # csrf_token به نتیجه اضافه می‌شود
            résultat['csrf_token'] = generate_csrf()

        ….

        # پاسخ HTTP ارسال می‌شود
        return response, status_code

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

        # نتیجهٔ متد
        return erreur, résultat, type_response1
  • خطوط ۳۹–۴۴: بررسی‌های اولیه روی اکشن انجام می‌شوند. بعداً به این موضوع باز خواهیم گشت. متد استاتیک [MainController.check_action] یک توییپ با سه عنصر برمی‌گرداند:
    • [erreur]: اگر خطایی تشخیص داده شده باشد True، در غیر این صورت False؛
    • [résultat]: در صورت وقوع خطا، یک نتیجه خطا؛ در غیر این صورت None؛
    • [type_response1]: نوع (json, xml, html, None) جلسه، همان‌طور که در جلسه یافت می‌شود؛
  • خط ۳۹: هیچ بررسی‌ای انجام نمی‌شود تا مشخص شود که آیا اقدام، ASV است یا [afficher-vue-liste-erreurs] که لیست خطاها را نمایش می‌دهد. در واقع، اگر در طول این اقدام خطایی یافت می‌شد، کاربر به اقدام [/afficher-vue-liste-erreurs] بازگردانده می‌شد که منجر به یک حلقه بی‌نهایت از هدایت‌ها می‌شد؛
  • متد استاتیک [check_action] به شرح زیر است:

    @staticmethod
    def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
        # عمل فعلی بازیابی می‌شود
        action = params[1]

        # در ابتدا هیچ خطا و هیچ نتیجه‌ای وجود ندارد
        erreur = False
        résultat = None

        # نوع جلسه باید قبل از انجام برخی اقدامات مشخص باشد ADS
        type_response1 = session.get('typeResponse')
        if type_response1 is None and action != "init-session":
            #یک خطا ثبت شده است
            résultat = {"action": action, "état"101,
                        "réponse"["pas de session en cours. Commencer par action [init-session]"]}
            erreur = True

        #برای انجام برخی اقدامات ADS، باید احراز هویت شده باشید
        user = session.get('user')
        if user is None and action not in ["init-session",
                                           "authentifier-utilisateur",
                                           "afficher-vue-authentification"]:
            # خطا را یادداشت کنید
            résultat = {"action": action, "état"101,
                        "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
            erreur = True

        # فقط برخی اقدامات از یک نما امکان‌پذیر است
        if not erreur and action != "init-session":
            # در حال حاضر هیچ اقدامی ممکن نیست
            actions_possibles = None
            #قالب نمای آینده را در جلسه بازیابی کنید، اگر وجود داشته باشد
            modèle = session.get('modèle')
            # اگر قالب یافت شد، عملیات ممکن آن را بازیابی کنید
            if modèle:
                actions_possibles = modèle.get('actions_possibles')
            # اگر فهرستی از اقدامات ممکن وجود داشته باشد، بررسی کنید که اقدام فعلی در آن گنجانده شده باشد
            if actions_possibles and action not in actions_possibles:
                #خطا را ثبت کنید
                résultat = {"action": action, "état"151,
                            "réponse"[f"action [{action}] incorrecte dans l'environnement actuel"]}
                erreur = True


        # خطا؟
        if not erreur and config['parameters']['with_csrftoken']:
            #بررسی اعتبار توکن CSRF
            # csrf_token آخرین عنصر در مسیر است
            csrf_token = params.pop()
            try:
                # اگر csrf_token نامعتبر باشد، یک استثنا پرتاب خواهد شد
                validate_csrf(csrf_token)
            except ValidationError as exception:
                # توکن CSRF نامعتبر
                résultat = {"action": action, "état"121"réponse"[f"{exception}"]}
                #خطا ثبت می‌شود
                erreur = True

        # نتیجهٔ متد
        return erreur, résultat, type_response1
  • خط ۲: متد [check_action] چندین بررسی بر اعتبار اقدام جاری انجام می‌دهد؛
  • خطوط ۶–۲۶، ۴۵–۵۷: نسخه‌های قبلی این بررسی‌ها را قبلاً انجام داده‌اند؛
  • خطوط ۲۸–۴۲: یک بررسی جدید اضافه شده است. سیستم بررسی می‌کند که آیا اقدام فعلی با توجه به وضعیت فعلی برنامه امکان‌پذیر است یا خیر. اگر اقدام فعلی امکان‌پذیر نباشد، یک کد وضعیت ۱۵۱ تولید می‌شود (خط ۴۰)، که تضمین می‌کند اقدام فعلی به نمای خطاهای غیرمنتظره هدایت شود؛

35.2.6. پاسخ جدید HTML

Image

تغییرات فعلی فقط بر جلسات HTML تأثیر می‌گذارد. جلسات jSON و XML تحت تأثیر قرار نمی‌گیرند. کلاس [HtmlResponse] به شرح زیر به‌روزرسانی می‌شود:


#وابستگی‌ها

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):
        #پاسخ HTML به کد وضعیت بازگردانده‌شده توسط کنترلر بستگی دارد
        état = résultat["état"]

        # ما بررسی می‌کنیم که آیا وضعیت توسط یک اکشن تولید شده است ASV
        #در این صورت، یک نما باید نمایش داده شود
        asv_configs = config['mvc']["asv"]
        trouvé = False
        i = 0
        #من لیست ویوها را مرور می‌کنم
        nb_views = len(asv_configs)
        while not trouvé and i < nb_views:
            # نما شمارهٔ i
            asv_config = asv_configs[i]
            #گزارش‌های مرتبط با نمای شماره i
            états = asv_config["états"]
            #آیا گزارشی که به دنبال آن هستید در میان گزارش‌های مرتبط با نمای شمارهٔ i قرار دارد؟
            if état in états:
                trouvé = True
            else:
                # نما بعدی
                i += 1

        # یافت شد؟
        if trouvé:
            # این یک اقدام ASV است – باید ویویی نمایش داده شود که قالب آن از قبل در جلسه موجود است
            #کد پاسخ HTML تولید می‌شود
            html = render_template(asv_config["view_name"], modèle=session['modèle'])
            #پاسخ HTTP ساخته می‌شود
            response = make_response(html)
            response.headers['Content-Type'] = 'text/html; charset=utf-8'
            #نتیجه بازگردانده می‌شود
            return response, status_code

        #یافت نشد – این یک کد وضعیت برای عملی است ADS
        # این با یک هدایت دنبال می‌شود
        redirected = False
        for ads in config['mvc']['ads']:
            #وضعیت‌هایی که نیاز به هدایت مجدد دارند
            états = ads["états"]
            if état in états:
                #یک هدایت وجود دارد
                redirected = True
                break
        # واژه‌نامه هدایت برای خطاهای غیرمنتظره
        if not redirected:
            ads = config['mvc']['view_erreurs']

        #آیا این یک هدایت به یک اقدام ASD یا ASV است؟
        #اگر قالب وجود داشته باشد، آنگاه هدایت به عملیاتی با شناسه ASV است
        #در این صورت قالب نمای V باید محاسبه شود، زیرا این نما توسط اقدام ASV نمایش داده خواهد شد
        model_for_view = ads.get("model_for_view")
        if model_for_view:
            #محاسبهٔ قالب برای نمای بعدی
            modèle = model_for_view.get_model_for_view(request, session, config, résultat)
            # قالب برای نمای بعدی در جلسه بارگذاری می‌شود
            session['modèle'] = modèle

            #اکنون باید هدایت 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
  • خطوط ۱۸–۳۵: سیستم بررسی می‌کند که آیا وضعیت تولید شده توسط آخرین اقدام اجرا شده، وضعیت یک اقدام ASV است؛
  • خطوط ۳۶–۴۶: اگر چنین باشد، نمای V مرتبط با اقدام ASV با استفاده از قالبی که در جلسه مرتبط با کلید [‘modèle’] یافت می‌شود، نمایش داده می‌شود؛
  • خطوط ۴۸–۶۰: وقتی به این نقطه می‌رسیم، می‌دانیم که وضعیت تولید شده توسط آخرین اقدام اجرا شده، وضعیت یک اقدام ADS است. این امر سپس یک تغییر مسیر را فعال می‌کند. ما در فایل پیکربندی به دنبال تعریف این تغییر مسیر می‌گردیم؛
  • خط ۶۲: در این نقطه، پیکربندی لازم برای انجام هدایت فراهم است. دو حالت وجود دارد:
    • این یک هدایت به اکشن دیگری، ADS است. در این حالت، هیچ قالب ویوی برای محاسبه وجود ندارد؛
    • این یک هدایت به اکشن ASV است. در این حالت، یک قالب نما باید محاسبه شود (خطوط 67–68). این قالب سپس در جلسه بارگذاری می‌شود (خط 70)؛
  • خطوط ۷۲–۷۶: هدایت URL محاسبه می‌شود؛
  • خطوط ۷۸–۷۹: پاسخ هدایت به کلاینت ارسال می‌شود؛

35.3. آزمایش‌ها

تست‌های زیر را با استفاده از یک مرورگر وب انجام دهید:

  • از برنامه به صورت عادی استفاده کنید. بررسی کنید که تنها کدهای URL که توسط مرورگر نمایش داده می‌شوند، URL، ASV و [/afficher-vue-nom_de_la_vue] هستند؛
  • صفحات را تازه‌سازی کنید (F5) و توجه کنید که همان صفحه دوباره نمایش داده می‌شود. هیچ عارضه جانبی وجود ندارد؛

علاوه بر این، از کلاینت [impots/http-clients/09] استفاده کنید. از آنجا که تغییرات انجام‌شده تنها بر جلسات HTML تأثیر می‌گذارند، کلاینت‌های [main, main2, main3, Test1HttpClientDaoWithSession, Test2HttpClientDaoWithSession] باید به کار خود ادامه دهند.

اکنون بیایید به موردی نگاه کنیم که انجام این عمل امکان‌پذیر نیست. نمای زیر نمایش داده می‌شود:

Image

به جای [1]، وارد URL [/supprimer-simulation/1] می‌شویم. عمل [/supprimer-simulation] یکی از عملیات ارائه‌شده توسط نما که عملیات ۱ تا ۴ هستند، نیست. بنابراین رد خواهد شد. پاسخ سرور به شرح زیر است:

Image