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:
# syspath configuration
import syspath
config['syspath'] = syspath.configure(config)
# application setup
import parameters
config['parameters'] = parameters.configure(config)
# database configuration
import database
config["database"] = database.configure(config)
# instantiation of application layers
import layers
config['layers'] = layers.configure(config)
# configuration MVC of the [web] layer
config['mvc'] = {}
# web] layer controller configuration
import controllers
config['mvc'].update(controllers.configure(config))
# actions ASV (Action Show View)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# actions ADS (Action Do Something)
import ads_actions
config['mvc'].update(ads_actions.configure(config))
# response configuration HTTP
import responses
config['mvc'].update(responses.configure(config))
# route configuration
import routes
routes.configure(config)
# we return the configuration
return config
|
- الأسطر 37–39: يتولى الوحدة النمطية [routes] (السطر 38) تكوين المسارات (السطر 39)؛
يتطور ملف المسارات بدون رمز CSRF [configs/routes_without_csrftoken] على النحو التالي:
| # dependencies
from flask import redirect, request, session, url_for
from flask_api import status
# application configuration
config = {}
# the front controller
def front_controller() -> tuple:
# forward the request to the main controller
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# application root
def index() -> tuple:
# redirect to /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:
# execute the controller associated with the action
return front_controller()
# authenticate-user
def authentifier_utilisateur() -> tuple:
# execute the controller associated with the action
return front_controller()
# calculate-tax
def calculer_impot() -> tuple:
# execute the controller associated with the action
return front_controller()
…
|
تم حذف المسارات من الملف. ولم يتبق سوى الوظائف المرتبطة بها.
يلقى ملف المسارات الذي يحتوي على رمز CSRF [configs/routes_with_csrftoken] نفس المصير:
| # dependencies
from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf
# configuration
config = {}
# the front controller
def front_controller() -> tuple:
# forward the request to the main controller
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
# application root
def index() -> tuple:
# redirect to /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:
# execute the controller associated with the action
return front_controller()
# authenticate-user
def authentifier_utilisateur(csrf_token: str) -> tuple:
# execute the controller associated with the action
return front_controller()
…
# init-session-without-csrftoken for json and xml clients
def init_session_without_csrftoken(type_response: str) -> tuple:
# redirect to /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):
# route settings
# flask application
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
config['app'] = app
# import routes from web application
if config['parameters']['with_csrftoken']:
import routes_with_csrftoken as routes
else:
import routes_without_csrftoken as routes
# inject configuration into routes
routes.config = config
# the application's URL prefix
prefix_url = config["parameters"]["prefix_url"]
# token CSRF
with_csrftoken = config["parameters"]['with_csrftoken']
if with_csrftoken:
csrftoken_param = f"/<string:csrf_token>"
else:
csrftoken_param = ""
# flask application routes
# application root
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)
# authenticate-user
app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
view_func=routes.authentifier_utilisateur)
# calculate-tax
app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impot)
# batch tax calculation
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)
# delete-simulation
app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
view_func=routes.supprimer_simulation)
# end of session
app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
view_func=routes.fin_session)
# display-calculation-tax
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)
# afficher-vue-calcul-impot
app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_calcul_impot)
# display-view-authentication
app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_authentification)
# display-view-list-simulations
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_simulations)
# display-view-error_list
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_erreurs)
# special case
if with_csrftoken:
# init-session-without-csrftoken for json and xml clients
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):
# path elements are retrieved
params = request.path.split('/')
action = params[1]
|
لم يعد هذا الكود يعمل في حالة وجود بادئة، على سبيل المثال [/do/lister-simulations]. في هذه الحالة، ستكون الإجراء في السطر 3 هو [do] وبالتالي سيكون غير صحيح.
نقوم بتعديل هذا الرمز على النحو التالي:
| def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# path elements are retrieved
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:
# we encapsulate the paged data in the model
modèle = {}
# application status
état = résultat["état"]
# the model depends on the state
…
# csrf token
modèle['csrf_token'] = super().get_csrftoken(config)
# possible actions from the view
modèle['actions_possibles'] = […]
# we render the model
return modèle
|
الآن سيحتوي النموذج على مفتاح إضافي، وهو بادئة عنوان URL:
| def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# we encapsulate the paged data in the model
modèle = {}
# application status
état = résultat["état"]
# the model depends on the state
…
# csrf token
modèle['csrf_token'] = super().get_csrftoken(config)
# possible actions from the view
modèle['actions_possibles'] = […]
# URL prefix
modèle["prefix_url"] = config["parameters"]["prefix_url"]
# we render the model
return modèle
|
36.5. الأجزاء الجديدة

يجب تعديل جميع الأجزاء التي تحتوي على عناوين URL.
جزء [v-authentication]
<!-- form HTML - post its values with the [authenticate-user] action -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
<!-- title -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
جزء [v-calcul-impot]
<!-- form HTML posted -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
<!-- 12-column message on blue background -->
…
</form>
مقتطف [v-liste-simulations]
…
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
…
<!-- simulation table -->
<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 menu -->
<nav class="nav flex-column">
<!-- display a list of links 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):
# the HTML response depends on the status code returned by the controller
état = résultat["état"]
…
# now it's time to generate the URL redirection, not forgetting the CSRF token if requested
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# redirect response
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):
…
# now it's time to generate the URL redirection, not forgetting the CSRF token if requested
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# redirect response
return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
|
36.7. الاختبارات

دعونا نضيف البادئة التالية إلى ملف التكوين [parameters]:
…
# token csrf
"with_csrftoken": True,
# bases gérées MySQL (mysql), PostgreSQL (pgres)
"databases": ["mysql", "pgres"],
# préfixe des URL de l'application
# mettre la chaîne vide si on ne veut pas de préfixe ou /préfixe sinon
"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؛
مع هذا التغيير، من المفترض أن تعمل جميع اختبارات وحدة التحكم.