36. تمرين تطبيقي: الإصدار 16

36.1. مقدمة
تتخذ URL في تطبيقنا حاليًا الشكل [/action/param1/param2/…]. نود أن نتمكن من إضافة بادئة إلى هذه الرموز URL. على سبيل المثال، باستخدام البادئة [/do]، سنحصل على رموز URL بالصيغة [/do/action/param1/param2/…].
36.2. التكوين الجديد للمسارات

- إلى [2]، سيتم تعديل حساب المسارات؛
- في [1]، يتم تعديل الملف [config] ليعكس هذا التغيير؛
يصبح التكوين [config] كما يلي:
def configure(config: dict) -> dict:
# تكوين مسار النظام
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 لطبقة [web]
config['mvc'] = {}
# تكوين وحدات التحكم في الطبقة [web]
import controllers
config['mvc'].update(controllers.configure(config))
# الإجراءات ASV (إجراء عرض)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# الإجراءات ADS (إجراء "Do Something")
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
- الأسطر 37-39: تتولى الوحدة النمطية [routes] (السطر 38) تكوين المسارات (السطر 39)؛
يتغير ملف المسارات بدون رمز 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)
# بدء الجلسة
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)
# تبدء الجلسة
app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
view_func=routes.init_session)
# بدء الجلسة بدون رمز CSRF
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)
# lister-simulations
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)
# الحصول على بيانات المسؤول
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)
- الأسطر 6-8: يتم إنشاء تطبيق Flask وإدراجه في التكوين؛
- الأسطر 10-14: يتم استيراد ملف المسارات المناسب للحالة. تجدر الإشارة إلى أن الملف المستورد لا يحتوي في الواقع على أي مسارات. فهو لا يحتوي سوى على الدوال المرتبطة بها؛
- السطر 17: تحتاج الوظائف المرتبطة بمسارات التوجيه إلى معرفة تكوين التطبيق؛
- السطر 20: نلاحظ البادئة URL. يمكن أن تكون هذه البادئة فارغة؛
- الأسطر 22-27: المسارات التي تحتوي على الرمز المميز CSRF لها معلمة إضافية مقارنة بتلك التي لا تحتوي عليه. لإدارة هذا الاختلاف، نستخدم المتغير [csrftoken_param]:
- تحتوي على سلسلة فارغة إذا لم تكن هناك علامة CSRF في المسارات؛
- وتحتوي على السلسلة [/<string:csrf_token>] في حالة وجود الرمز CSRF؛
- الأسطر 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]. في هذه الحالة، ستكون العملية، في السطر 3، هي [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}}">
<!-- رسالة مكونة من 12 عمودًا على خلفية زرقاء -->
…
</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]
<!-- قائمة Bootstrap -->
<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 (pgres)
"databases": ["mysql", "pgres"],
# بادئة URL الخاصة بالتطبيق
# تعيين السلسلة فارغة إذا لم تكن هناك حاجة إلى بادئة، أو /بادئة في الحالات الأخرى
"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": {
…
}
- السطر 3: أصبح URL الخاص بالخادم يتضمن الآن البادئة الخاصة بـ URL؛
بعد إجراء هذا التعديل، يجب أن تعمل جميع اختبارات وحدة التحكم.