35. Практичне завдання: версія 15
35.1. Вступ
Ця версія призначена для вирішення проблем, пов’язаних з оновленням сторінок додатка в браузері (F5). Розглянемо приклад. Користувач щойно видалив симуляцію з id=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 (Action Do Something), що змінюють стан додатка. Дії ADS зазвичай мають параметри в URL або в тілі запиту;
- дії ASV (Action Show View), що відображають представлення без зміни стану додатка. Кількість дій ASV дорівнюватиме кількості представлень V. Дії ASV не мають жодних параметрів;
- дотепер дії ADS виконувалися, а потім завершувалися відображенням подання V після підготовки його моделі M. Відтепер вони додаватимуть шаблон M цього виду до сесії та вимагатимуть від браузера перенаправлення на дію ASV, яка відповідає за відображення виду V;
- види V будуть відображатися лише після виконання дії ASV. Вони братимуть свою модель із сесії;
Перевага цього методу полягає в тому, що браузер відображатиме 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()
У рядках 1–23 ми створили чотири маршрути для чотирьох дій ASV:
- [/afficher-vue-authentification], рядок 8, відображає вікно автентифікації;
- [/afficher-vue-calcul-impot], рядок 2, відображає вікно розрахунку податку;
- [/afficher-vue-liste-simulations], рядок 14, відображає вікно моделювання;
- [/afficher-vue-liste-erreurs], рядок 20, відображає вікно несподіваних помилок;
Те саме робимо у файлі [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 повинен відображати вікно автентифікації;
- код 1400 повинен відображати вікно розрахунку податку;
- код 1200 повинен відображати вікно моделювання;
- код 1300 повинен відображати вікно несподіваних помилок;
35.2.3. Нова конфігурація MVC

Оскільки конфігурація MVC стала занадто великою, її було розділено на чотири файли:
- [controllers]: список контролерів C додатка MVC;
- [ads_actions]: містить список дій ADS (Action Do Something);
- [asv_actions]: перелік дій ASV (Action Show View);
- [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] у рядках 6–35 пов’язує вигляд із кожною дією 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 (Action Do Something)
ads = [
{
"états": [
400, # /успішне завершення сеансу
],
# перенаправлення на дію 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, # /перелік-симуляцій успішно
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,
}
- рядки 11–51: список дій ADS (Action Do Something). Тут містяться всі дії з попередніх версій. Однак їхнє функціонування змінилося:
- вони не відображають вигляд V. Вони лише готують модель M цього вигляду V;
- вони ініціюють відображення подання V шляхом перенаправлення на дію ASV, пов’язану з поданням V;
- не всі дії ADS призводять до перенаправлення на дію ASV: у рядках 12–18 дії 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 шару [web]
config['mvc'] = {}
# конфігурація контролерів шару [web]
import controllers
config['mvc'].update(controllers.configure(config))
# дії ASV (Action Show View)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# дії ADS (Action Do Something)
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
- Нововведення міститься у рядку 17: кожна модель генеруватиме список можливих дій, коли буде відображатися вікно 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'])
…
# дія /afficher-vue-liste-erreurs є особливою
# не проводиться жодної перевірки, інакше існує ризик потрапити в нескінченний цикл перенаправлень
erreur = False
if action != "afficher-vue-liste-erreurs":
# у разі помилки [результат] — це результат, який слід надіслати клієнту
(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
- рядки 39–44: виконуються перші перевірки дії. Ми повернемося до цього пізніше. Статичний метод [MainController.check_action] повертає трійку елементів:
- [erreur]: True, якщо виявлено помилку, інакше — False;
- [résultat]: результат помилки, якщо (помилка == True), інакше — None;
- [type_response1]: тип (json, xml, html, None) сесії, який було знайдено в сесії;
- рядок 39: перевірка не виконується, якщо дія — це дія 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
- рядок 2: метод [check_action] виконує кілька перевірок щодо правильності поточної дії;
- рядки 6–26, 45–57: попередні версії вже виконували ці перевірки;
- рядки 28–42: додається нова перевірка. Перевіряється, чи можлива поточна дія в поточному стані додатка. Якщо поточна дія неможлива, генерується стан 151 (рядок 40), що гарантує перенаправлення поточної дії на сторінку несподіваних помилок;
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
- рядки 18–35: перевіряється, чи стан, створений останньою виконаною дією, відповідає дії ASV;
- рядки 36–46: якщо так, то відображається представлення V, пов’язане з дією ASV, використовуючи як шаблон той, що знайдено в сесії, пов’язаній із ключем [‘modèle’];
- рядки 48–60: коли доходимо до цього місця, ми знаємо, що стан, створений останньою виконаною дією, відповідає дії ADS. Тоді це спричинить перенаправлення. У файлі конфігурації шукаємо визначення цього перенаправлення;
- рядок 62: коли ми доходимо до цього місця, маємо конфігурацію перенаправлення, яке потрібно виконати. Є два випадки:
- це перенаправлення на іншу дію ADS. У цьому випадку обчислювати шаблон подання не потрібно;
- це перенаправлення на дію ASV. У цьому випадку потрібно обчислити шаблон подання (рядки 67–68). Потім цей шаблон заноситься в сесію (рядок 70);
- рядки 72–76: обчислюється URL перенаправлення;
- рядки 78–79: відправляється відповідь про перенаправлення клієнту;
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] не входить до переліку дій, запропонованих у вікні (дії 1–4). Тому вона буде відхилена. Відповідь сервера така:
