36. تمرین عملی: نسخه ۱۶

36.1. مقدمه
عمل URL در برنامه ما در حال حاضر به شکل [/action/param1/param2/…] است. ما میخواهیم بتوانیم این URL را پیشوندگذاری کنیم. برای مثال، با پیشوند [/do]، ما URL را در قالب [/do/action/param1/param2/…] خواهیم داشت.
36.2. پیکربندی جدید مسیر

- به [2]، محاسبه مسیر اصلاح خواهد شد؛
- در [1]، فایل [config] برای انعکاس این تغییر اصلاح میشود؛
پیکربندی [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))
#پیکربندی مسیر
import routes
routes.configure(config)
#اعمال پیکربندی
return config
- خطوط ۳۷–۳۹: ماژول [routes] (خط ۳۸) مسئول پیکربندی مسیرها (خط ۳۹) است؛
فایل مربوط به مسیرهای بدون توکن، CSRF [configs/routes_without_csrftoken]، به شرح زیر تغییر مییابد:
#وابستگیها
from flask import redirect, request, session, url_for
from flask_api import status
# پیکربندی برنامه
config = {}
# کنترلکنندهٔ جلویی
def front_controller() -> tuple:
#درخواست به کنترلر اصلی ارجاع داده میشود
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
#ریشهٔ برنامه
def index() -> tuple:
# ارسال مجدد به /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
#init-session
def init_session(type_response: str) -> tuple:
#کنترلکنندهی مرتبط با اکشن اجرا میشود
return front_controller()
#احراز هویت کاربر
def authentifier_utilisateur() -> tuple:
# اجرای کنترلر مرتبط با اقدام
return front_controller()
#محاسبه-مالیات
def calculer_impot() -> tuple:
# اجرای کنترلر مرتبط با اقدام
return front_controller()
…
فایل از مسیرهای خود خالی شده است. تنها توابع مرتبط با آنها باقی ماندهاند.
فایلهای مسیر حاوی توکنها، CSRF و [configs/routes_with_csrftoken]، تحت همان فرآیند قرار میگیرند:
# وابستگیها
from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf
# پیکربندی
config = {}
#کنترلکنندهٔ جلویی
def front_controller() -> tuple:
#درخواست به کنترلکننده اصلی ارجاع داده میشود
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
#ریشهٔ برنامه
def index() -> tuple:
# ارسال مجدد به /init-session/html
return redirect(url_for("init_session", type_response="html", csrf_token=generate_csrf()), status.HTTP_302_FOUND)
#init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
# اجرای کنترلر مرتبط با اقدام
return front_controller()
#احراز هویت کاربر
def authentifier_utilisateur(csrf_token: str) -> tuple:
# اجرای کنترلر مرتبط با اکشن
return front_controller()
…
#init-session-without-csrftoken برای کلاینتهای JSON و XML
def init_session_without_csrftoken(type_response: str) -> tuple:
# ارسال مجدد به /init-session/type_response
return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
status.HTTP_302_FOUND)
مسیرها توسط ماژول زیر محاسبه میشوند: [configs/routes]:
from flask import Flask
def configure(config: dict):
#پیکربندی مسیر
# برنامه Flask
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
config['app'] = app
#وارد کردن مسیرهای برنامه وب
if config['parameters']['with_csrftoken']:
import routes_with_csrftoken as routes
else:
import routes_without_csrftoken as routes
#تزریق پیکربندی به مسیرها
routes.config = config
# پیشوند برنامه URL
prefix_url = config["parameters"]["prefix_url"]
# توکن CSRF
with_csrftoken = config["parameters"]['with_csrftoken']
if with_csrftoken:
csrftoken_param = f"/<string:csrf_token>"
else:
csrftoken_param = ""
# مسیرهای برنامه Flask
#ریشهٔ برنامه
app.add_url_rule(f'{prefix_url}/', methods=['GET'],
view_func=routes.index)
# 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)
# احراز هویت کاربر
app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
view_func=routes.authentifier_utilisateur)
# محاسبه-مالیات
app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impot)
#محاسبه دستهای مالیات
app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impots)
# فهرست-شبیهسازیها
app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.lister_simulations)
# حذف-شبیهسازی
app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
view_func=routes.supprimer_simulation)
# پایان-جلسه
app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
view_func=routes.fin_session)
#نمایش محاسبه مالیات
app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_calcul_impot)
# get-admindata
app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
view_func=routes.get_admindata)
#مشاهده-نمای-کلی-محاسبه-مالیات
app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_calcul_impot)
# نمایش-نما-احراز-هویت
app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_authentification)
#نمایش-فهرست-مشاهده-شبیهسازی
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_simulations)
#نمایش-فهرست-نمایش-خطا
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_erreurs)
# مورد خاص
if with_csrftoken:
# راهاندازی جلسه بدون توکن CSRF برای مشتریان JSON و XML
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
view_func=routes.init_session_without_csrftoken)
- خطوط ۶–۸: برنامه Flask ایجاد شده و به پیکربندی اضافه میشود؛
- خطوط ۱۰–۱۴: فایل مسیرها (routes) مناسب با موقعیت وارد میشود. توجه داشته باشید که فایل واردشده در واقع هیچ مسیری را در خود ندارد؛ بلکه تنها شامل توابعی است که با آنها مرتبط هستند؛
- خط ۱۷: توابع مرتبط با مسیرها باید پیکربندی برنامه را بدانند؛
- خط ۲۰: به پیشوند URL توجه کنید. این پیشوند ممکن است خالی باشد؛
- خطوط 22–27: مسیرهایی که دارای توکن CSRF هستند، در مقایسه با آنهایی که این توکن را ندارند، یک پارامتر اضافی دارند. برای رسیدگی به این تفاوت، متغیر [csrftoken_param] استفاده میشود:
- اگر توکن CSRF در مسیرها وجود نداشته باشد، حاوی یک رشتهٔ خالی است؛
- اگر توکن CSRF وجود داشته باشد، حاوی رشته [/<string:csrf_token>] است؛
- خطوط 29–96: هر مسیر در برنامه با تابعی از فایل مسیرها که در خطوط 10–14 وارد شده است مرتبط است؛
36.3. کنترلکنندههای جدید

در نسخه قبلی، همه کنترلکنندهها اقدام در حال پردازش را به شرح زیر دریافت میکردند:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
#بازیابی عناصر مسیر
params = request.path.split('/')
action = params[1]
این کد دیگر در صورتی که پیشوندی وجود داشته باشد، برای مثال [/do/lister-simulations]، کار نخواهد کرد. در این حالت، اکشن در خط ۳، [do] خواهد بود و بنابراین نادرست خواهد بود.
ما این کد را به شکل زیر تبدیل میکنیم:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
#بازیابی عناصر مسیر
prefix_url = config["parameters"]["prefix_url"]
params = request.path[len(prefix_url):].split('/')
action = params[1]
ما این کار را در تمام کنترلرها انجام میدهیم.
36.4. قالبهای جدید

در نسخه قبلی، هر کلاس قالب یک قالب را به صورت زیر تولید میکرد:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# دربرگیری دادههای صفحه در یک قالب
modèle = {}
#وضعیت برنامه
état = résultat["état"]
# قالب به وضعیت وابسته است
…
# توکن CSRF
modèle['csrf_token'] = super().get_csrftoken(config)
# اقدامات ممکن از دیدگاه
modèle['actions_possibles'] = […]
# قالب رندر میشود
return modèle
از این پس، قالب یک کلید اضافی با پیشوند URL خواهد داشت:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# دربرگرفتن دادههای صفحه در قالب
modèle = {}
#وضعیت برنامه
état = résultat["état"]
# مدل به وضعیت وابسته است
…
#توکن CSRF
modèle['csrf_token'] = super().get_csrftoken(config)
# اقدامات ممکن از دیدگاه
modèle['actions_possibles'] = […]
#پیشوند برای URL
modèle["prefix_url"] = config["parameters"]["prefix_url"]
# قالب رندر میشود
return modèle
36.5. قطعات جدید

تمام قطعات حاوی URL باید اصلاح شوند.
قطعه [v-authentification]
<!-- فرم HTML – مقادیر آن را با استفاده از اکشن [authentifier-utilisateur] ارسال میکنیم -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
<!-- عنوان -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
قطعه [v-calcul-impot]
<!-- HTML فرم ارسال شد -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
<!-- پیامی در ۱۲ ستون روی پسزمینه آبی -->
…
</form>
قطعه [v-liste-simulations]
…
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
…
<!-- جدول شبیهسازی -->
<table class="table table-sm table-hover table-striped">
…
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
قطعه [v-menu]
<!-- منوی بوتاسترپ -->
<nav class="nav flex-column">
<!--نمایش فهرستی از پیوندها HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
36.6. پاسخ جدید HTML

در نسخه قبلی، کد پاسخ HTML با یک هدایت پایان یافت:
class HtmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
#پاسخ HTML به کد وضعیت بازگرداندهشده توسط کنترلر بستگی دارد
état = résultat["état"]
…
#اکنون باید هدایت URL تولید شود، بدون فراموش کردن توکن CSRF در صورت نیاز
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
#پاسخ هدایت
return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
این کد اکنون به شرح زیر است:
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
…
# اکنون باید کد هدایت URL را تولید کنیم، بدون فراموش کردن توکن CSRF در صورت درخواست
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# پاسخ هدایت
return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
36.7. آزمایشها

بیایید پیشوند زیر را به فایل پیکربندی [parameters] اضافه کنیم:
…
# توکن CSRF
"with_csrftoken": True,
# پایگاههای داده مدیریتشده: MySQL (MySQL), PostgreSQL (PostgreSQL)
"databases": ["mysql", "pgres"],
#پیشوند برنامه برای URL
# اگر نیازی به پیشوند نباشد، رشته را خالی تنظیم کنید، در غیر این صورت از /prefix استفاده کنید
"prefix_url": "/do",
…
بیایید برنامه را اجرا کرده و سپس URL [http://localhost:5000/do] را درخواست کنیم؛ پاسخ به شرح زیر است:

- در [1]، پیشوند URL؛
- به [2]، توکن برای CSRF؛
برنامه را میتوان با استفاده از تستهای کنسول برای [http-clients/09] نیز آزمایش کرد:

در فایلهای پیکربندی [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": {
…
}
- خط ۳: URL سرور اکنون شامل پیشوند URL است؛
پس از اعمال این تغییر، تمام تستهای کنسول باید کار کنند.