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

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

در اینجا یک مثال دیگر آورده شده است. کاربر به تازگی مالیاتی را محاسبه کرده است:
- در [1]، URL و [/calculer-impot] که به تازگی با یک POST پرسوجو شدهاند؛
اگر کاربر صفحه را تازهسازی کند (F5)، پیغام هشدار دریافت میکند:

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

لینکهای ارائهشده در این صفحه عبارتند از:
- [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. پیادهسازی

پوشه [impots/http-servers/10] در ابتدا با کپی کردن پوشه [impots/http-servers/09] ایجاد میشود. سپس تغییر داده میشود.
35.2.1. مسیرهای جدید

در هر دو فایل [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. کنترلکنندههای جدید

کنترلکننده [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

از آنجا که بیش از حد بزرگ شده بود، پیکربندی 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. قالبهای جدید

قالبها اطلاعات جدیدی تولید خواهند کرد. برای مثال، قالب نمای احراز هویت را در نظر بگیرید:
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 بازگردید. در مورد نمای احراز هویت:

- میتوانیم ببینیم که نمای احراز هویت تنها یک اقدام را ارائه میدهد، یعنی اقدام مربوط به دکمه [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. کنترلکننده اصلی جدید

کنترلکننده [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

تغییرات فعلی فقط بر جلسات 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] باید به کار خود ادامه دهند.
اکنون بیایید به موردی نگاه کنیم که انجام این عمل امکانپذیر نیست. نمای زیر نمایش داده میشود:

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