38. تمرين تطبيقي: الإصدار 18
38.1. التنفيذ

يتم الحصول على الملف [impots/http-servers/13] في البداية عن طريق نسخ الملف [impots/http-servers/12] ثم يتم تعديله جزئيًا.
نبدأ بإضافة معلمة جديدة في الملف [configs/parameters]:
…
# رمز CSRF
"with_csrftoken": False,
# قواعد البيانات المدارة: MySQL (mysql)، PostgreSQL (pgres)
"databases": ["mysql", "pgres"],
# بادئة URL الخاصة بالتطبيق
# ضع السلسلة فارغة إذا كنت لا تريد بادئة أو /بادئة بخلاف ذلك
"prefix_url": "/do",
# عنوان URL الجذر لخادم Apache - ضع السلسلة فارغة للتشغيل خارج Apache
"application_root": "/impots"
…
في السطر 10، سيمثل المعلمة [application_root] الاسم المستعار WSGI للخادم الافتراضي Apache.
باستخدام هذا المعلمة، يمكننا تصحيح الأمر الخاص بـ [responses/HtmlResponse] الذي تسبب في الخطأ:

…
# الآن يجب إنشاء رابط إعادة التوجيه URL دون نسيان الرمز المميز CSRF إذا طُلب
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# استجابة إعادة التوجيه
return redirect(f"{config['parameters']['application_root']}{config['parameters']['prefix_url']}{ads['to']}{csrf_token}")
, status.HTTP_302_FOUND
- السطر 9: أضفنا جذر التطبيق في بداية هدف إعادة التوجيه URL؛
كما يتعين علينا تصحيح جميع الأجزاء بحيث تبدأ العناوين URL التي تحتوي عليها بجذر التطبيق (أو الاسم المستعار WSGI):

المقطع [v-authentification]
<!-- نموذج HTML - يتم إرسال قيمه باستخدام الإجراء [authentifier-utilisateur] -->
<form method="post" action="{{modèle.application_root}}{{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.application_root}}{{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.application_root}}{{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.application_root}}{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
تستخدم جميع المقاطع المذكورة أعلاه القالب [modèle.application_root]. في الوقت الحالي، لا توجد المفتاح [application_root] في القوالب التي تم إنشاؤها بواسطة فئات القوالب.

تصبح الفئة [AbstractBaseModelForView]، وهي الفئة الأم لجميع الفئات التي تُنشئ نموذجًا، كما يلي:
from abc import abstractmethod
from flask import Request
from flask_wtf.csrf import generate_csrf
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class AbstractBaseModelForView(InterfaceModelForView):
@abstractmethod
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
pass
def update_model(self, modèle: dict, config: dict):
# حساب الرمز المميز CSRF
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# تحديث القالب الذي تم تمريره كمعلمة
modèle.update({
# رمز CSRF
'csrf_token': csrf_token،
# prefix_url
'prefix_url': configQZXW2HTMLBWyJwYXJhbWV0ZXJzIl0ZQXQZXW2HTMLBWyJwcmVmaXhfdXJsIl0ZQX,
# application_root
'application_root': configQZXW2HTMLBWyJwYXJhbWV0ZXJzIl0ZQXQZXW2HTMLBWyJhcHBsaWNhdGlvbl9yb290Il0ZQX,
})
- السطر 15: تتمثل وظيفة الطريقة [update_model] في إدراج العروض في النموذج:
- السطر 24: الرمز CSRF؛
- السطر 26: البادئة لـ URL؛
- السطر 28: جذر التطبيق أو الاسم المستعار WSGI؛
تستدعي الفئات الفرعية الأربع الفئة الأم باستخدام الكود التالي:
…
# الإجراءات الممكنة من العرض
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
# إنهاء النموذج بواسطة الفئة الأم
super().update_model(modèle, config)
# عرض النموذج
return modèle
- السطر 6: تستدعي كل فئة فرعية فئتها الأم لتحديث النموذج الذي أنشأته؛
الإصدار 18 جاهز. نستعيد خادمي Apache الافتراضيين من الإصدار 17 ونقوم بتعديلهما:

لم يتم تعديل الملفين [flask-impots-withXX.conf] إلا في نقطة واحدة فقط:
# مجلد البرنامج النصي .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/13/apache"
# اسم الموقع الإلكتروني الذي تم تكوينه بواسطة هذا الملف
# وهنا سيُسمى flask-impots-withmysql
# ستكون عناوين URL من النوع http(s)://flask-impots-withmysql/المسار
define SITE "flask-impots-withmysql"
# أدخل العنوان IP 127.0.0.1 للموقع SITE في الملف c:/windows/system32/drivers/etc/hosts
# أدخل هنا مسارات مكتبات Python المراد استخدامها - وافصل بينها بفواصل
# هنا مكتبات بيئة Python الافتراضية
WSGIPythonPath "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
# موقع Python الرئيسي - ضروري فقط في حالة تثبيت عدة إصدارات من Python
# WSGIPythonHome "C:/Program Files/Python38"
# URL HTTP
<VirtualHost *:80>
# مع الاسم المستعار / فإن URL ستكون على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، ستكون صيغة URL على النحو التالي: /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في parameters.py
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
…
</VirtualHost>
# URL المؤمنة باستخدام HTTPS
<VirtualHost *:443>
# مع الاسم المستعار / ستكون URL على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، ستكون عناوين URL على الشكل /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في parameters.py
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
..
</VirtualHost>
- في السطر 12، نستخدم الآن المجلد [impots/http-servers/13/apache].
نحن جاهزون الآن لاختبار الإصدار 18 الذي تم ترحيله إلى Apache. التكوين هو كما يلي:
- الاسم المستعار WSGI هو /impots في ملفَي تكوين الخوادم الافتراضية؛
- في ملف الإعداد [configs/parameters]، المعلمات هي كما يلي:
# رمز CSRF
"with_csrftoken": False,
# بادئة URL الخاصة بالتطبيق
# ضع السلسلة فارغة إذا كنت لا تريد بادئة أو /بادئة بخلاف ذلك
"prefix_url": "/do",
# عنوان URL الجذر لخادم Apache - ضع السلسلة فارغة للتشغيل خارج Apache
"application_root": "/impots"
يتم تشغيل خادم Apache بالإضافة إلى الخادمين SGBD. يتم طلب URL و [https://flask-impots-withmysql/impots/do]. رد الخادم هو كما يلي:

نحصل بالفعل على صفحة المصادقة التي لم نتمكن من الحصول عليها في الإصدار السابق. يعمل باقي التطبيق بشكل طبيعي.
الآن نختبر الخادم الافتراضي الآخر. نطلب URL و [https://flask-impots-withpgres/impots/do]. رد الخادم هو كما يلي:

يؤدي كل من مفهوم الاسم المستعار WSGI ومفهوم البادئة URL نفس الدور. أحد هذين المفهومين زائد عن الحاجة. وبالتالي، لإضافة البادئة [/impots/do] إلى عناوين URL الخاصة بخادم Apache، يمكن اتباع ثلاث طرق:
1 – [WGSIAlias /impots] و [prefix_url=’/do’]؛
2 - [WGSIAlias /] و [prefix_url=’/impots/do’]؛
3 - [WGSIAlias /impots/do] و [prefix_url=’’]؛
38.2. اختبارات وحدة التحكم
نستخدم مرة أخرى اختبارات وحدة التحكم الخاصة بالعميل [http-clients/09]:

- يجب تعديل URL الخاص بالخادم في التكوينات [1] و [3]؛
- يجب إجراء تعديل على الطبقة [dao] لتدعم بروتوكول HTTPS الخاص بخادم Apache؛
في الملفات [config]، يصبح URL الخاص بالخادم كما يلي:
"server": {
# "urlServer": "http://127.0.0.1:5000",
# "urlServer": "http://127.0.0.1:5000/do"،
"urlServer": "https://flask-impots-withmysql/impots/do",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
…
}
},
# وضع التصحيح
"debug": True,
# csrf_token
"with_csrftoken": False,
- السطر 4: URL الجديد الخاص بالخادم. ولأول مرة في هذا المستند، يستخدم العميل بروتوكول HTTPS؛
تتطور الفئة [ImpôtsDaoWihHttpSession] من الطبقة [dao] على النحو التالي:
…
# مرحلة الطلب/الاستجابة
def get_response(self, method: str, url_service: str, data_value: dict = None, json_value=None):
# [method]: الطريقة HTTP أو GET أو POST
# [url_service]: خدمة URL
# [data]: معلمات POST بتنسيق x-www-form-urlencoded
# [json]: معلمات POST بتنسيق json
# [cookies]: ملفات تعريف الارتباط التي يجب تضمينها في الطلب
# يجب أن تكون هناك جلسة عمل XML أو JSON، وإلا فلن نتمكن من معالجة الاستجابة
if self.__session_type not in ['json', 'xml']:
raise ImpôtsError(73, "il n'y a pas de session valide en cours")
# يتم إضافة الرمز المميز CSRF إلى رمز الخدمة URL
if self.__csrf_token:
url_service = f"{url_service}/{self.__csrf_token}"
# تنفيذ الطلب
response = requests.request(method,
url_service,
data=data_value,
json=json_value,
cookies=self.__cookies,
allow_redirects=True,
# لبروتوكول https
verify=False)
# وضع التصحيح؟
if self.__debug:
# المسجل
if not self.__logger:
self.__logger = self.__config['logger']
# تسجيل الدخول
self.__logger.write(f"{response.text}\n")
…
# يتم إرجاع النتيجة
return résultat['réponse']
- في السطر 26، تمت إضافة المعلمة [verify=False] بسبب بروتوكول HTTPS الذي يستخدمه خادم Apache. تستطيع الوحدة النمطية [requests] (السطر 19) إدارة بروتوكول HTTPS بشكل أصلي. بشكل افتراضي، يتحقق هذا المكون من صحة شهادة الأمان التي يرسلها إليه الخادم HTTPS، ويطلق استثناءً إذا كانت الشهادة المستلمة غير صالحة. وهذا هو الحال هنا حيث يرسل خادم Apache الخاص بـ Laragon شهادة موقعة ذاتيًا. لتجنب الاستثناء، نستخدم المعلمة [verify=False] لإخبار الوحدة النمطية [requests] بعدم إثارة استثناء. عندئذٍ يكتفي [requests] بعرض تحذير (warning) على وحدة التحكم.
بعد إجراء هذه التعديلات، يجب أن تعمل جميع اختبارات وحدة التحكم.