Skip to content

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:

Image

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:

Image

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:

Image

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:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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:

Image

  • 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

Image

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

Image

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:

Image

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:

Image