35. Ćwiczenie praktyczne: wersja 15
35.1. Wprowadzenie
Ta wersja ma na celu rozwiązanie problemów związanych z odświeżaniem stron aplikacji w przeglądarce (F5). Weźmy przykład. Użytkownik właśnie usunął symulację o id=3:

Po usunięciu w przeglądarce URL wyświetla się [/supprimer-simulation/3/…]. Jeśli użytkownik odświeży stronę (F5), odtwarzane są kolejno URL i [1]. W związku z tym ponownie wysyłane jest żądanie usunięcia symulacji o identyfikatorze id=3. Wynik jest wówczas następujący:

Oto kolejny przykład. Użytkownik właśnie obliczył podatek:
- w [1], URL oraz [/calculer-impot], które właśnie zostały wywołane za pomocą POST;
Jeśli użytkownik odświeży stronę (F5), pojawi się komunikat ostrzegawczy:

Operacja odświeżenia strony powoduje ponowne wykonanie ostatniego żądania wysłanego przez przeglądarkę, w tym przypadku [POST /calculer-impot]. Gdy użytkownik poprosi o ponowne wykonanie żądania o numerze POST, przeglądarki wyświetlają ostrzeżenie podobne do powyższego. Ostrzeżenie to informuje, że przeglądarka ponownie wykonuje czynność, która została już wcześniej wykonana. Załóżmy, że żądanie o numerze POST spowodowało dokonanie zakupu – ponowne wykonanie tej czynności byłoby niefortunne.
Ponadto ograniczymy użytkownikowi możliwość wpisywania sekwencji URL w przeglądarce. Weźmy na przykład jeden z poprzednich widoków:

Linki dostępne na tej stronie to:
- [Liste des simulations] powiązany z URL i [/lister-simulations];
- [Fin de session] powiązany z URL i [/fin-session];
- [Valider] powiązany z URL (nie widoczny powyżej) [/calculer-impot];
Gdy wyświetli się widok obliczeń podatku, będziemy akceptować wyłącznie akcje [/lister-simulations, /fin-session, /calculer-impot]. Jeśli użytkownik wpisze w przeglądarce inną akcję, zostanie zgłoszony błąd. Tego typu weryfikację przeprowadzimy dla wszystkich czterech widoków aplikacji.
Proponujemy rozwiązać problem odświeżania stron w następujący sposób:
- rozróżnimy dwa rodzaje akcji:
- akcje ADS (Action Do Something), które zmieniają stan aplikacji. Akcje ADS zazwyczaj zawierają parametry w URL lub w treści żądania;
- akcje typu ASV (Action Show View), wyświetlające widok bez zmiany stanu aplikacji. Liczba akcji typu ASV będzie równa liczbie widoków V. Akcje typu ASV nie mają żadnych parametrów;
- akcje ADS do tej pory były wykonywane, a następnie kończyły się wyświetleniem widoku V po przygotowaniu jego modelu M. Od tej pory będą one umieszczać szablon M widoku w sesji i żądać od przeglądarki przekierowania do akcji ASV odpowiedzialnej za wyświetlenie widoku V;
- widoki V będą wyświetlane dopiero po zakończeniu działania ASV. Będą one pobierać swój szablon z sesji;
Zaletą tej metody jest to, że przeglądarka wyświetli w pasku adresu adres URL ASV. Odświeżenie strony spowoduje ponowne wykonanie akcji ASV. Akcja ta nie zmienia stanu aplikacji i korzysta z szablonu sesyjnego. Dzięki temu ta sama strona zostanie ponownie wyświetlona bez żadnych niepożądanych skutków ubocznych. Ostatecznie, z powodu przekierowań, użytkownik zobaczy w przeglądarce wyłącznie akcje typu URL wynikające z akcji ASV i będzie miał wrażenie, że przechodzi od strony do strony;
35.2. Implementacja

Katalog [impots/http-servers/10] jest początkowo tworzony poprzez skopiowanie katalogu [impots/http-servers/09]. Następnie jest on modyfikowany.
35.2.1. Nowe ścieżki

W obu plikach [routes_with_csrftoken] i [routes_without_csrftoken] należy utworzyć cztery ścieżki dla czterech akcji ASV, które wyświetlają cztery widoki. Pozostałe ścieżki pozostają bez zmian.
W pliku [routes_with_csrftoken]:
# wyświetlenie widoku obliczeń podatku
@app.route('/afficher-vue-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_vue_calcul_impot(csrf_token: str) -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
# wyświetlenie widoku uwierzytelniania
@app.route('/afficher-vue-authentification/<string:csrf_token>', methods=['GET'])
def afficher_vue_authentification(csrf_token: str) -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
# wyświetl-widok-lista-symulacji
@app.route('/afficher-vue-liste-simulations/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_simulations(csrf_token: str) -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
# wyświetl-widok-lista_błędów
@app.route('/afficher-vue-liste-erreurs/<string:csrf_token>', methods=['GET'])
def afficher_vue_liste_erreurs(csrf_token: str) -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
W wierszach 1–23 utworzyliśmy cztery trasy dla czterech akcji ASV:
- [/afficher-vue-authentification], wiersz 8, wyświetla widok uwierzytelniania;
- [/afficher-vue-calcul-impot], wiersz 2, wyświetla widok obliczeń podatku;
- [/afficher-vue-liste-simulations], wiersz 14, wyświetla widok symulacji;
- [/afficher-vue-liste-erreurs], wiersz 20, wyświetla widok nieoczekiwanych błędów;
To samo wykonujemy w pliku [routes_with_csrftoken] dotyczącym tras bez żetonów:
# trasy ASV -------------------------
# wyświetl-widok-obliczenia-podatku
@app.route('/afficher-vue-calcul-impot', methods=['GET'])
def afficher_vue_calcul_impot() -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
# wyświetl-widok-uwierzytelniania
@app.route('/afficher-vue-authentification', methods=['GET'])
def afficher_vue_authentification() -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
# wyświetl-widok-lista-symulacji
@app.route('/afficher-vue-liste-simulations', methods=['GET'])
def afficher_vue_liste_simulations() -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
# wyświetl-widok-lista_błędów
@app.route('/afficher-vue-liste-erreurs', methods=['GET'])
def afficher_vue_liste_erreurs() -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
35.2.2. Nowe kontrolery

Kontroler [AfficherVueAuthentificationController] wykonuje akcję 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):
# pobierane są elementy ścieżki
params = request.path.split('/')
action = params[1]
# zmiana widoku – wystarczy ustawić kod stanu
return {"action": action, "état": 1100, "réponse": ""}, status.HTTP_200_OK
Wspomniano, że akcje ASV nie mają żadnych parametrów i nie zmieniają stanu aplikacji. Wystarczy wyświetlić żądany widok, ustawiając w wierszu 14 kod stanu.
Kontroler [AfficherVueCalculImpotController] wykonuje akcję 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):
# pobierane są elementy ścieżki
params = request.path.split('/')
action = params[1]
# zmiana widoku – wystarczy ustawić kod stanu
return {"action": action, "état": 1400, "réponse": ""}, status.HTTP_200_OK
Kontroler [AfficherVueListeSimulationsController] wykonuje akcję 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):
# pobieramy elementy ścieżki
params = request.path.split('/')
action = params[1]
# zmiana widoku – wystarczy ustawić kod stanu
return {"action": action, "état": 1200, "réponse": ""}, status.HTTP_200_OK
Kontroler [AfficherVueListeErreursController] wykonuje akcję 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):
# pobieramy elementy ścieżki
params = request.path.split('/')
action = params[1]
# zmiana widoku – wystarczy ustawić kod stanu
return {"action": action, "état": 1300, "réponse": ""}, status.HTTP_200_OK
Podsumujmy kody stanu:
- kod 1100 powinien wyświetlać ekran uwierzytelniania;
- kod 1400 powinien wyświetlać ekran obliczania podatku;
- kod 1200 powinien wyświetlać widok symulacji;
- kod 1300 powinien wyświetlać widok nieoczekiwanych błędów;
35.2.3. Nowa konfiguracja MVC

Ponieważ konfiguracja MVC stała się zbyt obszerna, została podzielona na cztery pliki:
- [controllers]: lista kontrolerów C aplikacji MVC;
- [ads_actions]: zawiera listę akcji ADS (Action Do Something);
- [asv_actions]: lista akcji z pliku ASV (akcja „Show View”);
- [responses]: wyświetla listę klas odpowiedzi HTTP aplikacji;
Zacznijmy od najprostszego, pliku odpowiedzi HTTP [responses]:
def configure(config: dict) -> dict:
# konfiguracja aplikacji MVC
# odpowiedzi HTTP
from HtmlResponse import HtmlResponse
from JsonResponse import JsonResponse
from XmlResponse import XmlResponse
# różne typy odpowiedzi (json, xml, html)
responses = {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
}
# tworzymy słownik odpowiedzi HTTP
return {
# odpowiedzi HTTP
"responses": responses,
}
Bez niespodzianek.
Plik kontrolerów [controllers] również nie zawiera żadnych niespodzianek. Po prostu dodano do niego nowe kontrolery akcji ASV.
def configure(config: dict) -> dict:
# konfiguracja aplikacji MVC
# główny kontroler
from MainController import MainController
# kontrolery akcji 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
# kontrolery akcji ASV
from AfficherVueCalculImpotController import AfficherVueCalculImpotController
from AfficherVueAuthentificationController import AfficherVueAuthentificationController
from AfficherVueListeErreursController import AfficherVueListeErreursController
from AfficherVueListeSimulationsController import AfficherVueListeSimulationsController
# dozwolone akcje i ich kontrolerzy
controllers = {
# inicjowanie sesji obliczeniowej
"init-session": InitSessionController(),
# uwierzytelnianie użytkownika
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# link do widoku obliczeń podatku
"afficher-calcul-impot": AfficherCalculImpotController(),
# obliczenie podatku w trybie indywidualnym
"calculer-impot": CalculerImpotController(),
# obliczenie podatku w trybie zbiorczym
"calculer-impots": CalculerImpotsController(),
# lista symulacji
"lister-simulations": ListerSimulationsController(),
# usunięcie symulacji
"supprimer-simulation": SupprimerSimulationController(),
# zakończenie sesji obliczeniowej
"fin-session": FinSessionController(),
# pobieranie danych z urzędu skarbowego
"get-admindata": GetAdminDataController(),
# główny kontroler
"main-controller": MainController(),
# wyświetlenie ekranu uwierzytelniania
"afficher-vue-authentification": AfficherVueAuthentificationController(),
# wyświetlenie widoku obliczeń podatku
"afficher-vue-calcul-impot": AfficherVueCalculImpotController(),
# wyświetlanie widoku symulacji
"afficher-vue-liste-simulations": AfficherVueListeSimulationsController(),
# wyświetlenie widoku błędów
"afficher-vue-liste-erreurs": AfficherVueListeErreursController()
}
# konfiguracja kontrolerów
return {
# kontrolery
"controllers": controllers,
}
Konfiguracja [asv_actions] dla akcji ASV wygląda następująco:
def configure(config: dict) -> dict:
# konfiguracja aplikacji MVC
# widoki HTML i ich szablony zależą od stanu zwracanego przez kontroler
# akcje ASV (akcja „Pokaż widok”)
asv = [
{
# widok uwierzytelniania
"états": [
1100, # /wyświetl-widok-uwierzytelniania
],
"view_name": "views/vue-authentification.html",
},
{
# widok obliczeń podatku
"états": [
1400, # /wyświetl-widok-obliczenia-podatku
],
"view_name": "views/vue-calcul-impot.html",
},
{
# widok listy symulacji
"états": [
1200, # /wyświetl-widok-listy-symulacji
],
"view_name": "views/vue-liste-simulations.html",
},
{
# widok listy błędów
"états": [
1300, # /wyświetl-widok-listy-błędów
],
"view_name": "views/vue-erreurs.html",
},
]
# konfiguracja ASV
return {
# widoki i szablony
"asv": asv,
}
- Plik [asv_actions] zawiera cztery nowe akcje, których działanie przypominamy:
- nie mają żadnych parametrów;
- wyświetlają konkretny widok, którego szablon jest aktywny w sesji;
- lista [asv] w wierszach 6–35 przypisuje widok do każdej akcji ASV;
Plik [ads_actions] zawiera akcje ADS:
def configure(config: dict) -> dict:
# konfiguracja aplikacji MVC
# szablony widoków
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# akcje ADS (Action Do Something)
ads = [
{
"états": [
400, # /pomyślne zakończenie sesji
],
# przekierowanie do akcji ADS
"to": "/init-session/html",
},
{
"états": [
700, # /init-session – sukces
201, # /uwierzytelnianie-użytkownika – niepowodzenie
],
# przekierowanie do akcji ASV
"to": "/afficher-vue-authentification",
# szablon następującego widoku
"model_for_view": ModelForAuthentificationView()
},
{
"états": [
200, # /uwierzytelnianie-użytkownika zakończone sukcesem
300, # /obliczyć-podatek – operacja zakończona sukcesem
301, # /oblicz-podatek – niepowodzenie
800, # /wyświetl-obliczenia-podatku link
],
# przekierowanie do akcji ASV
"to": "/afficher-vue-calcul-impot",
# szablon następnego widoku
"model_for_view": ModelForCalculImpotView()
},
{
"états": [
500, # /wyświetl-listę-symulacji – operacja zakończona sukcesem
600, # /usuń-symulację – operacja zakończona sukcesem
],
# przekierowanie do akcji ASV
"to": "/afficher-vue-liste-simulations",
# szablon następującego widoku
"model_for_view": ModelForListeSimulationsView()
},
]
# widok nieoczekiwanych błędów
view_erreurs = {
# przekierowanie do akcji ASV
"to": "/afficher-vue-liste-erreurs",
# szablon następującego widoku
"model_for_view": ModelForErreursView()
}
# konfiguracja MVC
return {
# akcje ADS
"ads": ads,
# widok nieoczekiwanych błędów
"view_erreurs": view_erreurs,
}
- wiersze 11–51: lista akcji ADS (Action Do Something). Znajdują się tu wszystkie akcje z poprzednich wersji. Ich działanie uległo jednak zmianie:
- nie wyświetlają widoku V. Przygotowują jedynie model M tego widoku V;
- żądają wyświetlenia widoku V poprzez przekierowanie do akcji ASV powiązanej z widokiem V;
- nie wszystkie akcje ADS prowadzą do przekierowania do akcji ASV: w wierszach 12–18 akcja ADS [/fin-session] prowadzi do przekierowania do akcji ADS [/init-session/html]. Aby odróżnić przekierowania ADS -> ADS i ADS -> ASV, można skorzystać z wzorca [model_for_view]. W przypadku przekierowań ADS → ADS taki wzorzec nie istnieje;
Plik [main/config], zawierający wszystkie konfiguracje, zmienia się w następujący sposób:
def configure(config: dict) -> dict:
# konfiguracja ścieżki systemowej
import syspath
config['syspath'] = syspath.configure(config)
# konfiguracja aplikacji
import parameters
config['parameters'] = parameters.configure(config)
# konfiguracja bazy danych
import database
config["database"] = database.configure(config)
# instancjonowanie warstw aplikacji
import layers
config['layers'] = layers.configure(config)
# konfiguracja warstwy MVC
config['mvc'] = {}
# konfiguracja kontrolerów warstwy [web]
import controllers
config['mvc'].update(controllers.configure(config))
# akcje ASV (Action Show View)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# akcje ADS (akcja „Do Something”)
import ads_actions
config['mvc'].update(ads_actions.configure(config))
# konfiguracja odpowiedzi HTTP
import responses
config['mvc'].update(responses.configure(config))
# przekazujemy konfigurację
return config
35.2.4. Nowe szablony

Szablony będą generować nowe informacje. Weźmy na przykład szablon widoku uwierzytelniania:
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:
# enkapsulujemy dane strony w szablonie
modèle = {}
…
# token CSRF
modèle['csrf_token'] = super().get_csrftoken(config)
# możliwe działania z poziomu widoku
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
# zwracamy szablon
return modèle
- Nowością jest wiersz 17: każdy szablon wygeneruje listę możliwych działań, gdy zostanie wyświetlony widok V, dla którego jest szablonem M. Aby poznać te działania, należy powrócić do widoku V. W przypadku widoku uwierzytelniania:

- widać, że widok uwierzytelniania oferuje tylko jedną akcję, tę związaną z przyciskiem [Valider]. Ta akcja to [/authentifier-utilisateur];
Napisaliśmy:
# możliwe działania z poziomu widoku
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
Na powyższym widoku widać, że jeśli użytkownik odświeży stronę, akcja [1] [/afficher-vue-authentification] zostanie ponownie wykonana. Musi więc być ona autoryzowana. Można było jej nie autoryzować, ale w takim przypadku użytkownik napotykałby błąd przy każdym ponownym załadowaniu strony. Uznaliśmy, że nie jest to pożądane.
Możliwe akcje są umieszczone w szablonie widoku. Wiemy, że szablon ten zostanie zapisany w sesji.
Robimy to dla każdego z czterech widoków. Dostępne akcje są zatem następujące:
Widok uwierzytelniania
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
Widok obliczania podatku
# możliwe działania z poziomu widoku
modèle['actions_possibles'] = ["afficher-vue-calcul-impot", "calculer-impot", "lister-simulations","fin-session"]
Widok listy symulacji
# możliwe działania z poziomu widoku
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
Widok listy błędów
# możliwe działania z poziomu widoku
modèle['actions_possibles'] = ["afficher-vue-liste-erreurs", "afficher-calcul-impot", "lister-simulations", "fin-session"]
35.2.5. Nowy główny kontroler

Kontroler [MainController] ulega pewnym modyfikacjom:
# import zależności
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):
# wysyłamy wiadomość e-mail do administratora aplikacji
config_mail = config['parameters']['adminMail']
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# główny kontroler aplikacji
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# przetwarzanie żądania
type_response1 = None
logger = None
try:
# pobieranie elementów ścieżki
params = request.path.split('/')
# akcja jest pierwszym elementem
action = params[1]
# rejestruje
logger = Logger(config['parameters']['logsFilename'])
…
# akcja /wyświetl-widok-listy-błędów jest szczególna
# nie przeprowadza się żadnej weryfikacji, w przeciwnym razie istnieje ryzyko wejścia w nieskończoną pętlę przekierowań
erreur = False
if action != "afficher-vue-liste-erreurs":
# w przypadku błędu [wynik] jest wynikiem, który należy wysłać do klienta
(erreur, résultat, type_response1) = MainController.check_action(params, session, config)
# jeśli nie ma błędu – akcja zostaje wykonana
if not erreur:
# uruchamia się kontroler powiązany z akcją
controller = config['mvc']['controllers'][action]
résultat, status_code = controller.execute(request, session, config)
except BaseException as exception:
# wyjątki (nieoczekiwane)
résultat = {"action": action, "état": 131, "réponse": [f"{exception}"]}
erreur = True
finally:
pass
# błąd wykonania
if erreur:
# błędne żądanie
status_code = status.HTTP_400_BAD_REQUEST
if config['parameters']['with_csrftoken']:
# do wyniku dodawany jest csrf_token
résultat['csrf_token'] = generate_csrf()
….
# wysyłana jest odpowiedź HTTP
return response, status_code
@staticmethod
def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
…
# wynik metody
return erreur, résultat, type_response1
- wiersze 39–44: przeprowadzane są wstępne weryfikacje akcji. Wrócimy do tego później. Metoda statyczna [MainController.check_action] zwraca krotkę składającą się z trzech elementów:
- [erreur]: True, jeśli wykryto błąd, w przeciwnym razie False;
- [résultat]: wynik błędu, jeśli (błąd == True), w przeciwnym razie None;
- [type_response1]: typ (json, xml, html, None) sesji, typ znaleziony w sesji;
- wiersz 39: nie przeprowadza się żadnej weryfikacji, jeśli akcją jest akcja ASV [afficher-vue-liste-erreurs], która wyświetli listę błędów. W rzeczywistości, gdyby podczas tej akcji wykryto błąd, nastąpiłoby ponowne przekierowanie do akcji [/afficher-vue-liste-erreurs] i wpadlibyśmy w nieskończoną pętlę przekierowań;
- metoda statyczna [check_action] wygląda następująco:
@staticmethod
def check_action(params: list, session: LocalProxy, config: dict) -> (bool, dict, str):
# pobieramy bieżącą akcję
action = params[1]
# brak błędu i wyniku na początku
erreur = False
résultat = None
# przed wykonaniem niektórych akcji należy znać typ sesji ADS
type_response1 = session.get('typeResponse')
if type_response1 is None and action != "init-session":
# odnotowano błąd
résultat = {"action": action, "état": 101,
"réponse": ["pas de session en cours. Commencer par action [init-session]"]}
erreur = True
# w przypadku niektórych czynności ADS konieczne jest uwierzytelnienie
user = session.get('user')
if user is None and action not in ["init-session",
"authentifier-utilisateur",
"afficher-vue-authentification"]:
# odnotowano błąd
résultat = {"action": action, "état": 101,
"réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
erreur = True
# z poziomu widoku dostępne są tylko niektóre operacje
if not erreur and action != "init-session":
# na razie nie ma dostępnych akcji
actions_possibles = None
# pobieramy szablon przyszłego widoku z sesji, jeśli istnieje
modèle = session.get('modèle')
# jeśli znaleziono szablon, pobierane są dostępne dla niego akcje
if modèle:
actions_possibles = modèle.get('actions_possibles')
# jeśli mamy listę możliwych akcji, sprawdzamy, czy bieżąca akcja znajduje się na tej liście
if actions_possibles and action not in actions_possibles:
# odnotowuje się błąd
résultat = {"action": action, "état": 151,
"réponse": [f"action [{action}] incorrecte dans l'environnement actuel"]}
erreur = True
# błąd?
if not erreur and config['parameters']['with_csrftoken']:
# sprawdzamy poprawność tokenu CSRF
# csrf_token jest ostatnim elementem ścieżki
csrf_token = params.pop()
try:
# zostanie zgłoszony wyjątek, jeśli csrf_token jest nieprawidłowy
validate_csrf(csrf_token)
except ValidationError as exception:
# nieważny token CSRF
résultat = {"action": action, "état": 121, "réponse": [f"{exception}"]}
# odnotowano błąd
erreur = True
# wynik metody
return erreur, résultat, type_response1
- wiersz 2: metoda [check_action] przeprowadza szereg weryfikacji poprawności bieżącej akcji;
- wiersze 6–26, 45–57: poprzednie wersje już przeprowadzały te weryfikacje;
- wiersze 28–42: dodano nową weryfikację. Sprawdzane jest, czy bieżąca operacja jest możliwa w aktualnym stanie aplikacji. Jeśli bieżąca operacja nie jest możliwa, generowany jest kod stanu 151 (wiersz 40), który gwarantuje, że bieżąca operacja zostanie przekierowana do widoku nieoczekiwanych błędów;
35.2.6. Nowa odpowiedź HTML

Bieżące zmiany dotyczą wyłącznie sesji HTML. Sesje jSON lub XML pozostają bez zmian. Klasa [HtmlResponse] ulega następującym zmianom:
# zależności
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):
# odpowiedź HTML zależy od kodu stanu zwróconego przez kontroler
état = résultat["état"]
# sprawdzamy, czy stan został wygenerowany przez akcję ASV
# w takim przypadku należy wyświetlić widok
asv_configs = config['mvc']["asv"]
trouvé = False
i = 0
# przeglądamy listę widoków
nb_views = len(asv_configs)
while not trouvé and i < nb_views:
# widok nr i
asv_config = asv_configs[i]
# raporty powiązane z widokiem nr i
états = asv_config["états"]
# czy poszukiwany raport znajduje się wśród raportów powiązanych z widokiem nr i
if état in états:
trouvé = True
else:
# następny widok
i += 1
# znaleziono?
if trouvé:
# jest to akcja ASV – należy wyświetlić widok, którego szablon jest już aktywny w sesji
# generujemy kod odpowiedzi HTML
html = render_template(asv_config["view_name"], modèle=session['modèle'])
# tworzy się odpowiedź HTTP
response = make_response(html)
response.headers['Content-Type'] = 'text/html; charset=utf-8'
# zwracany jest wynik
return response, status_code
# nie znaleziono – jest to kod stanu akcji ADS
# po której nastąpi przekierowanie
redirected = False
for ads in config['mvc']['ads']:
# stany wymagające przekierowania
états = ads["états"]
if état in états:
# nastąpi przekierowanie
redirected = True
break
# słownik przekierowań na wypadek nieoczekiwanych błędów
if not redirected:
ads = config['mvc']['view_erreurs']
# czy jest to przekierowanie do akcji ASD lub ASV?
# jeśli istnieje szablon, to jest to przekierowanie do akcji ASV
# należy wówczas obliczyć szablon widoku V, który zostanie wyświetlony przez akcję ASV
model_for_view = ads.get("model_for_view")
if model_for_view:
# obliczenie szablonu następnego widoku
modèle = model_for_view.get_model_for_view(request, session, config, résultat)
# szablon jest zapisywany w sesji dla następnego widoku
session['modèle'] = modèle
# teraz należy wygenerować URL przekierowania, nie zapominając o tokenie CSRF, jeśli jest wymagany
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# odpowiedź przekierowania
return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
- wiersze 18–35: sprawdzane jest, czy stan wygenerowany przez ostatnią wykonaną akcję odpowiada akcji ASV;
- wiersze 36–46: jeśli tak, wyświetlany jest widok V powiązany z akcją ASV, przy czym jako szablon wykorzystywany jest szablon znaleziony w sesji powiązanej z kluczem [‘modèle’];
- wiersze 48–60: po dotarciu do tego miejsca wiadomo, że stan wygenerowany przez ostatnią wykonaną akcję odpowiada akcji ADS. Spowoduje to przekierowanie. W pliku konfiguracyjnym wyszukiwana jest definicja tego przekierowania;
- wiersz 62: gdy znajdziemy się w tym miejscu, mamy konfigurację przekierowania, które należy wykonać. Istnieją dwa przypadki:
- jest to przekierowanie do innej akcji ADS. W takim przypadku nie ma szablonu widoku do obliczenia;
- jest to przekierowanie do akcji ASV. W takim przypadku należy obliczyć szablon widoku (wiersze 67–68). Szablon ten jest następnie umieszczany w sesji (wiersz 70);
- wiersze 72–76: obliczany jest szablon przekierowania URL;
- wiersze 78–79: wysyłana jest odpowiedź przekierowania do klienta;
35.3. Testy
Przeprowadź następujące testy w przeglądarce:
- korzystaj z aplikacji w normalny sposób. Sprawdź, czy jedynymi kodami URL wyświetlanymi przez przeglądarkę są URL, ASV i [/afficher-vue-nom_de_la_vue];
- odśwież strony (F5) i sprawdź, czy wyświetla się ponownie ta sama strona. Nie występują żadne skutki uboczne;
Ponadto użyj klienta o numerze [impots/http-clients/09]. Ponieważ wprowadzone zmiany dotyczą wyłącznie sesji o numerach HTML, klienci o numerach [main, main2, main3, Test1HttpClientDaoWithSession, Test2HttpClientDaoWithSession] powinni nadal działać.
Teraz przyjrzyjmy się sytuacji, w której działanie jest niemożliwe. Wyświetla się następujący widok:

Zamiast [1] wpisujemy URL [/supprimer-simulation/1]. Akcja [/supprimer-simulation] nie znajduje się wśród akcji proponowanych w widoku, czyli akcji 1–4. Zostanie zatem odrzucona. Odpowiedź serwera jest następująca:
