32. Tryb HTML w wersji 12
Na początku wersji 12 zaznaczyliśmy, że będziemy rozwijać aplikację w kilku etapach. Napisaliśmy:
- na podstawie widoków aplikacji HTML zdefiniujemy akcje, które musi zaimplementować aplikacja internetowa. Wykorzystamy tutaj rzeczywiste widoki, ale mogłyby to być po prostu widoki na papierze;
- na podstawie tych działań zdefiniujemy URL usług aplikacji HTML;
- zaimplementujemy te URL usług przy pomocy serwera dostarczającego jSON. Pozwala to zdefiniować szkielet serwera internetowego bez konieczności zajmowania się stronami HTML, które mają być dostarczane. Przetestujemy te usługi URL za pomocą Postmana;
- następnie przetestujemy nasz serwer jSON za pomocą klienta konsolowego;
- gdy serwer jSON zostanie zatwierdzony, przejdziemy do pisania aplikacji HTML;
Mamy już działające serwery jSON i XML. Możemy teraz przejść do serwera HTML. Zobaczymy, że serwer ten wykorzystuje całą architekturę opracowaną dla serwerów jSON / XML i dodaje do niej obsługę widoków HTML.
32.1. Architektura MVC
Zaimplementujemy model architektury znany jako MVC (Model – Widok – Kontroler) w następujący sposób:
Przetwarzanie żądania klienta będzie przebiegało w następujący sposób:
- 1 – żądanie
Żądane URL będą miały postać http://machine:port/action/param1/param2/…. [Contrôleur principal] wykorzysta plik konfiguracyjny do „przekierowania” żądania do właściwego kontrolera. W tym celu wykorzysta pole [action] z URL. Pozostała część URL [param1/param2/…] składa się z opcjonalnych parametrów, które zostaną przekazane do akcji. C w MVC to w tym przypadku ciąg znaków [Contrôleur principal, Contrôleur / Action]. Jeśli żaden kontroler nie będzie w stanie obsłużyć żądanej akcji, serwer WWW odpowie, że żądana akcja URL nie została znaleziona.
- 2 – przetwarzanie
- Wybrana akcja [2a] może wykorzystać parametry parami, które przekazał jej [Contrôleur principal]. Mogą one pochodzić z dwóch źródeł:
- ze ścieżki [/param1/param2/…] obiektu URL,
- z parametrów umieszczonych w treści żądania klienta;
- podczas przetwarzania żądania użytkownika akcja może wymagać warstwy [métier] [2b]. Po przetworzeniu żądania klienta może ono wywołać różne odpowiedzi. Klasycznym przykładem jest:
- odpowiedź o błędzie, jeśli żądanie nie mogło zostać poprawnie przetworzone;
- w przeciwnym razie odpowiedź potwierdzająca;
- [Contrôleur / Action] zwróci swoją odpowiedź [2c] do głównego kontrolera wraz z kodem stanu. Kody te w jednoznaczny sposób odzwierciedlają stan, w jakim znajduje się aplikacja. Będzie to albo kod sukcesu, albo kod błędu;
- 3 – odpowiedź
- w zależności od tego, czy klient zażądał odpowiedzi jSON, XML czy HTML, moduł [Contrôleur principal] utworzy instancję odpowiedniego typu odpowiedzi [3a] i poprosi ją o wysłanie odpowiedzi do klienta. [Contrôleur principal] przekaże mu zarówno odpowiedź, jak i kod stanu dostarczone przez [Contrôleur / Action], który został wykonany;
- jeśli żądana odpowiedź jest typu jSON lub XML, wybrana odpowiedź sformatuje odpowiedź otrzymaną od [Contrôleur / Action] i wyśle ją za pośrednictwem [3c]. Klientem zdolnym do wykorzystania tej odpowiedzi może być skrypt konsolowy w języku Python lub skrypt JavaScript umieszczony na stronie o nazwie HTML;
- jeśli pożądana odpowiedź jest typu HTML, wybrana odpowiedź wybierze jeden z widoków HTML lub [Vuei] za pomocą podanego kodu stanu. Jest to widok V dla MVC. Jednemu kodowi stanu odpowiada jeden widok. Ten widok V wyświetli odpowiedź z [Contrôleur / Action], który został wykonany. Wykorzystuje ona HTML, CSS oraz JavaScript do formatowania danych tej odpowiedzi. Dane te nazywamy modelem widoku. To właśnie litera M w MVC. Klientem jest wówczas najczęściej przeglądarka;
32.2. Struktura drzewa skryptów serwera HTML

- w [1] – elementy statyczne serwera HTML;
- w [2-3] – widoki V serwera HTML. Fragmenty [2] to elementy wielokrotnego użytku w widokach [3];
- w [4] – folder, który posłuży do statycznego testowania widoków;
- w [5] znajduje się folder szablonów M dla widoków V, a M dla MVC;
32.3. Przedstawienie widoków
Aplikacja internetowa HTML wykorzystuje cztery widoki. Pierwszym widokiem jest widok uwierzytelniania:
- akcją prowadzącą do tego pierwszego widoku jest akcja [/init-session] [1];
- kliknięcie przycisku [Valider] uruchamia akcję [/authentifier-utilisateur] z dwoma parametrami przesłanymi metodą POST [2-3];
Widok obliczeń podatku:

- w [1] akcja [/authentifier-utilisateur], która wyświetla ten widok;
- w [2] kliknięcie przycisku [Valider] uruchamia akcję [/calculer-impot] z trzema przekazanymi parametrami [2-5];
- kliknięcie linku [6] uruchamia akcję [/lister-simulations] bez parametrów;
- kliknięcie linku [7] uruchamia akcję [/fin-session] bez parametrów;
Trzeci widok przedstawia symulacje przeprowadzone przez uwierzytelnionego użytkownika:

- w [1] akcja [/lister-simulations], która prowadzi do tego widoku;
- w [2] kliknięcie linku [Supprimer] uruchamia akcję [/supprimer-simulation] z jednym parametrem – numerem symulacji do usunięcia z listy;
- kliknięcie linku [3] uruchamia akcję [/afficher-calcul-impot] bez parametrów, która ponownie wyświetla widok obliczeń podatku;
- kliknięcie linku [4] uruchamia akcję [/fin-session] bez parametrów;
Czwarty widok zostanie nazwany widokiem nieoczekiwanych błędów:
- w [1]: użytkownik sam wpisał URL. Jednak w tym przykładzie nie było żadnych symulacji. Otrzymujemy zatem komunikat o błędzie [2]. Znamy ten komunikat. Pojawił się on już w przypadku jSON / XML. Ten rodzaj błędu nazwiemy błędem nieoczekiwanym, ponieważ nie może on wystąpić podczas normalnego użytkowania aplikacji. Błędy te mogą wystąpić tylko wtedy, gdy użytkownik sam wpisze kody URL;
- w przypadku nieoczekiwanego błędu linki [3-5] umożliwiają powrót do jednego z trzech pozostałych widoków;
Przypomnijmy różne kody serwisowe serwera URL: jSON / XML:
Akcja | Rola | Kontekst wykonania |
/init-session | Służy do określenia typu (json, xml, html) oczekiwanych odpowiedzi | Żądanie GET Może zostać wysłane w dowolnym momencie |
/authentifier-utilisateur | Zezwala lub nie zezwala użytkownikowi na zalogowanie się | Żądanie POST. Żądanie musi zawierać dwa parametry wysyłane metodą POST: [user, password] Może zostać wysłane tylko wtedy, gdy typ sesji (json, xml, html) jest znany |
/calculer-impot | Przeprowadza symulację obliczenia podatku | Żądanie POST. Żądanie musi zawierać trzy parametry wysyłane metodą POST: [marié, enfants, salaire] Może zostać wysłane tylko wtedy, gdy znany jest typ sesji (json, xml, html) i użytkownik jest uwierzytelniony |
/lister-simulations | Żądanie wyświetlenia listy symulacji przeprowadzonych od początku sesji | Żądanie GET. Może zostać wysłane tylko wtedy, gdy typ sesji (json, xml, html) jest znany, a użytkownik jest uwierzytelniony |
/usuń-symulację/numer | Usuwa symulację z listy symulacji | Żądanie GET. Może zostać wysłane tylko wtedy, gdy znany jest typ sesji (json, xml, html) i użytkownik jest uwierzytelniony |
/wyświetl-obliczenie-podatku | Wyświetla stronę HTML z obliczeniami podatku | Żądanie GET. Może zostać wysłane tylko wtedy, gdy znany jest typ sesji (json, xml, html) i użytkownik jest uwierzytelniony |
/zakończ-sesję | Zamyka sesję symulacji. | Z technicznego punktu widzenia poprzednia sesja internetowa zostaje usunięta, a tworzona jest nowa sesja Może zostać wysłane tylko wtedy, gdy znany jest typ sesji (json, xml, html) i użytkownik jest uwierzytelniony |
Te różne kody usług URL będą również wykorzystywane przez serwer HTML.
32.4. Konfiguracja widoków
Akcja jest przetwarzana przez kontroler. Kontroler ten zwraca krotkę (wynik, status_code), gdzie:
- [résultat] jest słownikiem kluczy [action, état, réponse];
- [status_code] to kod statusu odpowiedzi HTTP, która zostanie wysłana do klienta;
W sesji HTML strona wyświetlana w wyniku wykonania akcji zależy od kodu statusu zwróconego przez kontroler. Zależność ta jest odzwierciedlona w konfiguracji [config] w następujący sposób:
# widoki HTML i ich szablony zależą od stanu zwracanego przez kontroler
"views": [
{
# ekran uwierzytelniania
"états": [
# /początek-sesji zakończony sukcesem
700,
# /autentyfikacja-użytkownika nie powiodła się
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# widok obliczeń podatku
"états": [
# /autoryzacja-użytkownika zakończona sukcesem
200,
# /oblicz-podatek pomyślnie
300,
# /obliczyć-podatek – niepowodzenie
301,
# /wyświetlenie-obliczeń-podatku
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# widok listy symulacji
"états": [
# /wyświetl-listę-symulacji
500,
# /usuń-symulację
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# widok nieoczekiwanych błędów
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# przekierowania
"redirections": [
{
"états": [
400, # /pomyślne zakończenie sesji
],
# przekierowanie do
"to": "/init-session/html",
}
],
}
- wiersze 2–40: [views] to lista widoków. Rozważmy widok z wierszy 3–13:
- wiersz 11: wyświetlany widok V;
- wiersz 12: instancja klasy odpowiedzialna za wygenerowanie modelu M tego widoku;
- wiersze 5–10: stany, które prowadzą do tego widoku;
- wiersze 3–13: widok uwierzytelniania;
- wiersze 14–28: widok obliczania podatku;
- wiersze 29–39: widok listy symulacji;
- wiersze 42–46: widok nieoczekiwanych błędów;
- wiersze 49–57: niektóre raporty prowadzą do widoku poprzez przekierowanie. Dotyczy to raportu 400, który odpowiada pomyślnie zakończonej akcji [/fin-session]. Należy wówczas przekierować klienta do akcji [http://machine:port/chemin/init-session/html];
Poniżej przedstawiamy poszczególne widoki.
32.5. Widok uwierzytelniania

32.5.1. Opis widoku
Widok uwierzytelniania wygląda następująco:

Widok składa się z dwóch elementów, które nazwiemy fragmentami:
- fragment [1] jest generowany przez fragment [v-bandeau.html];
- fragment [2] jest generowany przez fragment [v-authentification.html];
Widok uwierzytelniania jest generowany przez następującą stronę [vue-authentification.html]:
<!-- dokumentu HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Wymagane metatagi -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- baner -->
{% include "fragments/v-bandeau.html" %}
<!-- układ dwukolumnowy -->
<div class="row">
<div class="col-md-9">
{% include "fragments/v-authentification.html" %}
</div>
</div>
<!-- w przypadku błędu – wyświetla się komunikat o błędzie -->
{% if modèle.error %}
<div class="row">
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Uwagi
- wiersz 2: dokument HTML rozpoczyna się od tego wiersza;
- wiersze 3–36: strona HTML jest zawarta w tagach <html> </html>;
- wiersze 4–11: nagłówek (head) dokumentu HTML;
- wiersz 6: tag <meta charset> wskazuje tutaj, że dokument jest zakodowany w UTF-8;
- wiersz 7: tag <meta name=’viewport’> określa początkowy sposób wyświetlania widoku: na całej szerokości ekranu, na którym jest wyświetlany (width), w jego pierwotnej skali (initial-scale), bez skalowania w celu dostosowania do mniejszego ekranu (shrink-to-fit);
- wiersz 9: tag <link rel=’stylesheet’> określa plik CSS, który reguluje wygląd strony. Wykorzystujemy tutaj framework CSS Bootstrap 4.4.1 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] ;
- wiersz 10: tag <title> określa tytuł strony:

- wiersze 13–35: treść strony internetowej jest zawarta w tagach <body></body>;
- wiersze 14–34: tag <div> wyznacza sekcję wyświetlanej strony. Wszystkie atrybuty [class] użyte w widoku odnoszą się do frameworka CSS Bootstrap. Tag <div class=’container’> (wiersz 14) wyznacza kontener Bootstrap;
- wiersz 26: wstawiono fragment [v-bandeau.html]. Fragment ten generuje nagłówek strony [1]. Opiszemy go wkrótce;
- wiersze 18–22: tag <div class=’row’> wyznacza wiersz Bootstrap. Wiersze te składają się z 12 kolumn;
- wiersz 19: tag <div class=’col-md-9’> wyznacza sekcję składającą się z 9 kolumn;
- wiersz 20: umieszczamy fragment [v-authentification.html], który wyświetla formularz uwierzytelniający [2] na stronie. Opiszemy go wkrótce;
- wiersze 24–33: kod HTML w tych wierszach jest używany tylko wtedy, gdy [modèle.error] ma wartość True. Zawsze będziemy postępować w ten sposób: szablon widoku HTML zostanie zamknięty w słowniku [modèle];
- wiersze 24–33: uwierzytelnianie kończy się niepowodzeniem, jeśli użytkownik wprowadzi nieprawidłowe dane logowania. W takim przypadku widok uwierzytelniania jest ponownie wyświetlany wraz z komunikatem o błędzie. Atrybut [modèle.error] wskazuje, czy należy wyświetlić ten komunikat o błędzie;
- wiersze 27–30: wyznaczają obszar o różowym tle (class="alert alert-danger") (wiersz 27);

- wiersz 28: tekst;
- wiersz 29: tag HTML <ul> (lista nieuporządkowana) wyświetla listę punktowaną. Każdy element listy musi mieć składnię <li>element</li>. Wyświetlana jest tutaj wartość [modèle.erreurs]. Wartość ta jest filtrowana (obecność znaku |) przez filtr [safe]. Domyślnie, gdy ciąg znaków ma zostać wysłany do przeglądarki, Flask „neutralizuje” wszystkie tagi HTML, które mogą się w nim znajdować, aby przeglądarka ich nie interpretowała. Czasami jednak chcemy, aby zostały one zinterpretowane. Tak będzie w przypadku, gdy ciąg znaków [modèle.erreurs] będzie zawierał tagi HTML <li> i </li>, które służą do wyodrębnienia elementu listy. W takim przypadku stosuje się filtr [safe], który informuje Flaska, że wyświetlany ciąg znaków jest bezpieczny (safe) i w związku z tym nie powinien neutralizować tagów HTML, które w nim znajdzie;
Z tego kodu zapamiętajmy elementy dynamiczne, które należy zdefiniować:
- [modèle.error]: do wyświetlania komunikatu o błędzie;
- [modèle.erreurs]: lista (w rozumieniu HTML tego terminu) komunikatów o błędach;
32.5.2. Fragment [v-bandeau.html]
Fragment [v-bandeau.html] wyświetla górny pasek wszystkich widoków aplikacji internetowej:

Kod fragmentu [v-bandeau.html] jest następujący:
<!-- Jumbotron Bootstrap -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="{{ url_for('static', filename='images/logo.jpg') }}" alt="Cerisier en fleurs"/>
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Komentarze
- wiersze 2–13: pasek nagłówkowy jest umieszczony w sekcji Bootstrap typu Jumbotron o nazwie [<div class="jumbotron">]. Ta klasa Bootstrap nadaje wyświetlanej treści specyficzny styl, aby ją wyróżnić;
- wiersze 3–12: wiersz Bootstrap;
- wiersze 4–6: obraz o nazwie [img] umieszczony w pierwszych czterech kolumnach wiersza;
- wiersz 5: składnia:
wykorzystuje funkcję [url_for] biblioteki Flask. W tym przypadku jej wartością będzie URL z pliku [images/logo.pg] znajdującego się w folderze [static];
- wiersze 7–11: pozostałe 8 kolumn w wierszu (przypomnijmy, że jest ich łącznie 12) posłużą do umieszczenia tekstu (wiersz 9) dużymi literami (<h1>, wiersze 8–10);
32.5.3. Fragment [v-authentification.html]
Fragment [v-authentification.html] wyświetla formularz uwierzytelniający aplikacji internetowej:

Kod fragmentu [v-authentification.html] jest następujący:
<!-- formularz HTML – wartości są wysyłane za pomocą akcji [authentifier-utilisateur] -->
<form method="post" action="/authentifier-utilisateur">
<!-- tytuł -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
<!-- formularz Bootstrap -->
<fieldset class="form-group">
<!-- pierwszy wiersz -->
<div class="form-group row">
<!-- nazwa -->
<label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
<div class="col-md-4">
<!-- pole tekstowe -->
<input type="text" class="form-control" id="user" name="user"
placeholder="Nom d'utilisateur" value="{{ modèle.login }}" required>
</div>
</div>
<!-- drugi wiersz -->
<div class="form-group row">
<!-- nazwa -->
<label for="password" class="col-md-3 col-form-label">Mot de passe</label>
<!-- pole wprowadzania tekstu -->
<div class="col-md-4">
<input type="password" class="form-control" id="password" name="password"
placeholder="Mot de passe" required>
</div>
</div>
<!-- przycisk typu [submit] w trzecim wierszu -->
<div class="form-group row">
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Komentarze
- wiersze 2–39: tag <form> wyznacza formularz HTML. Formularz ten ma zazwyczaj następujące cechy:
- definiuje pola wprowadzania danych (tagi <input> w wierszach 17 i 27);
- zawiera przycisk typu [submit] (wiersz 34), który wysyła wprowadzone wartości do pola URL wskazanego w atrybucie [action] tagu [form] (wiersz 2). Metoda HTTP używana do wysłania zapytania do tego URL jest określona w atrybucie [method] tagu [form] (wiersz 2);
- w tym przypadku, gdy użytkownik kliknie przycisk [Valider] (wiersz 34), przeglądarka prześle (wiersz 2) wartości wprowadzone w formularzu do URL [/authentifier-utilisateur] (wiersz 2);
- przesłane wartości to wartości wprowadzone przez użytkownika w polach wprowadzania danych w wierszach 17 i 27. Zostaną one przesłane w treści żądania HTTP, które przeglądarka wyśle w postaci [x-www-forl-urlencoded]. Nazwy parametrów [user, password] odpowiadają atrybutom [name] pól wprowadzania danych w wierszach 17 i 27;
- wiersze 5–7: sekcja Bootstrap służąca do wyświetlenia tytułu na niebieskim tle;
- wiersze 10–37: formularz Bootstrap. Wszystkie elementy formularza zostaną wówczas stylizowane w określony sposób;
- wiersze 12–20: definiują pierwszy wiersz Bootstrap formularza:
![]()
- wiersz 14 definiuje etykietę [1] w trzech kolumnach. Atrybut [for] tagu [label] łączy ten tekst z atrybutem [id] pola wprowadzania danych w wierszu 17;
- wiersze 15–19: umieszcza pole wprowadzania danych w układzie czterech kolumn;
- wiersze 17–18: znaczniki HTML i [input] opisują pole wprowadzania danych. Posiadają one kilka parametrów:
- [type=’text’]: jest to pole tekstowe. Można w nim wpisać dowolną treść;
- [class=’form-control’]: styl Bootstrap dla pola wprowadzania danych;
- [id=’user’]: identyfikator pola wprowadzania danych. Identyfikator ten jest zazwyczaj wykorzystywany przez CSS oraz kod JavaScript;
- [name=’user’]: nazwa pola tekstowego. Pod tą nazwą wartość wprowadzona przez użytkownika zostanie przesłana przez przeglądarkę [user=xx];
- [placeholder=’invite’]: tekst wyświetlany w polu wprowadzania danych, gdy użytkownik jeszcze nic nie wpisał;
![]()
- (ciąg dalszy)
- [value=’valeur’]: tekst „wartość” zostanie wyświetlony w polu wprowadzania danych, gdy tylko zostanie ono wyświetlone, a więc zanim użytkownik wprowadzi cokolwiek innego. Mechanizm ten jest wykorzystywany w przypadku błędu w celu wyświetlenia wpisu, który spowodował błąd. W tym przypadku wartością tą będzie wartość zmiennej [modèle.login];
- [required]: wymaga od użytkownika wprowadzenia wartości, aby formularz mógł zostać wysłany na serwer:
- wiersze 21–30: analogowy kod dla pola hasła;
- wiersz 27: [type=’password’] powoduje, że mamy pole do wprowadzania tekstu (można wpisać cokolwiek), ale wpisane znaki są ukryte:
![]()
- wiersze 32–36: trzeci wiersz Bootstrap dla przycisku [Valider];
- wiersz 34: ponieważ przycisk ten posiada atrybut [type=submit], kliknięcie na niego powoduje wysłanie przez przeglądarkę wprowadzonych wartości do serwera, tak jak wyjaśniono to wcześniej. Atrybut CSS [class="btn btn-primary"] powoduje wyświetlenie niebieskiego przycisku:
Pozostało nam wyjaśnić jeszcze jedną rzecz. W wierszu 2 atrybut [action="/authentifier-utilisateur"] definiuje niekompletny atrybut URL (nie zaczyna się on od http://machine:port/chemin). W naszym przykładzie wszystkie elementy URL w aplikacji mają postać [http://machine:port/chemin/action/param1/param2/..], gdzie [http://machine:port/chemin] jest korzeniem elementów URL usługi. W [action="/authentifier-utilisateur"] mamy absolutny identyfikator URL, tj. mierzony od korzenia identyfikatorów URL. Pełnym odpowiednikiem URL dla POST jest zatem [http://machine:port/chemin/authentifier-utilisateur] i właśnie tego użyje przeglądarka.
Należy zauważyć, że ten fragment wykorzystuje szablon [modèle.login].
32.5.4. Testy wizualne
Testy widoków można przeprowadzić na długo przed ich wdrożeniem do aplikacji. Chodzi tu o sprawdzenie ich wyglądu. Wszystkie widoki testowe umieścimy w folderze [tests_views] projektu:

Aby przetestować widok V [vue-authentification.html], musimy utworzyć model danych M, który będzie on wyświetlał. Robimy to za pomocą skryptu [test_vue_authentification.py]:
from flask import Flask, render_template, make_response
# aplikacja Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Strona główna URL
@app.route('/')
def index():
# dane strony są umieszczane w szablonie
modèle = {}
# identyfikator użytkownika
modèle["login"] = "albert"
# lista błędów
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
# tworzy się listę HTML błędów
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# wyświetlanie strony
return make_response(render_template("views/vue-authentification.html", modèle=modèle))
# strona główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Komentarze
- wiersze 1–3: tworzymy aplikację Flask, której jedynym celem jest wyświetlenie widoku [vue-authentification.html] (wiersz 22);
- wiersz 7: aplikacja posiada tylko jeden obiekt serwisowy URL;
- wiersze 9–20: widok uwierzytelniający zawiera części dynamiczne kontrolowane przez obiekt [modèle]. Obiekt ten nazywamy szablonem widoku. Zgodnie z jedną z dwóch definicji podanych dla skrótu MVC mamy tu do czynienia z M z MVC. Podczas definiowania widoku [vue-authentification.html] zidentyfikowaliśmy trzy wartości dynamiczne:
- [modèle.error]: wartość logiczna wskazująca, czy należy wyświetlić komunikat o błędzie;
- [modèle.erreurs]: lista HTML zawierająca komunikaty o błędach;
- [modèle.login]: nazwa użytkownika;
Musimy zatem zdefiniować te trzy wartości dynamiczne.
- wiersze 9–20: definiujemy trzy elementy dynamiczne widoku uwierzytelniania;
Aby przeprowadzić test, uruchamiamy skrypt [tests_views/test_vue_authentification.py] i żądamy URL [/localhost:5000/]:
Kontynuujemy te testy wizualne, aż będziemy zadowoleni z wyniku.

32.5.5. Obliczanie modelu widoku
Po ustaleniu wyglądu widoku można przystąpić do obliczenia modelu widoku w rzeczywistych warunkach. Modele widoków zostaną wygenerowane przez klasy zgromadzone w folderze [models_for_views]:

Każda klasa generująca model widoku będzie zgodna z następującym interfejsem [InterfaceModelForView]:
from abc import ABC, abstractmethod
from flask import Request
from werkzeug.local import LocalProxy
class InterfaceModelForView(ABC):
@abstractmethod
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
pass
- wiersze 8–10: metoda [get_model_for_view] odpowiada za wygenerowanie szablonu widoku zamkniętego w słowniku. W tym celu otrzymuje następujące informacje:
- [request, session, config] to te same parametry, które wykorzystuje kontroler akcji. Są one zatem przekazywane również do modelu;
- kontroler wygenerował wynik [résultat], który jest również przekazywany do modelu. Wynik ten zawiera ważny element [état], który wskazuje, jak przebiegło wykonanie bieżącej akcji. Model wykorzysta tę informację;
Widzieliśmy, że w konfiguracji aplikacji [config] kody stanu zwracane przez kontrolery są wykorzystywane do wskazania widoku HTML, który ma zostać wyświetlony:
# widoki HTML i ich szablony zależą od stanu zwracanego przez kontroler
"views": [
{
# widok uwierzytelniania
"états": [
# /init-session – operacja zakończona powodzeniem
700,
# /uwierzytelnianie-użytkownika – niepowodzenie
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# widok obliczeń podatku
"états": [
# /autoryzacja-użytkownika zakończona sukcesem
200,
# /oblicz-podatek pomyślnie
300,
# /obliczyć-podatek – niepowodzenie
301,
# /wyświetl-obliczenia-podatku
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# widok listy symulacji
"états": [
# /wyświetl-listę-symulacji
500,
# /usuń-symulację
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# widok nieoczekiwanych błędów
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# przekierowania
"redirections": [
{
"états": [
400, # /pomyślne zakończenie sesji
],
# przekierowanie do
"to": "/init-session/html",
}
],
}
To właśnie kody stanu [700, 201] (wiersze 7 i 9) powodują wyświetlenie ekranu uwierzytelniania. Aby poznać znaczenie tych kodów, można skorzystać z testów [Postman] przeprowadzonych w aplikacji jSON:
- [init-session-json-700]: 700 to kod stanu po pomyślnym wykonaniu akcji [init-session]: wyświetlany jest wówczas pusty formularz uwierzytelniający;
- [authentifier-utilisateur-201]: 201 to kod stanu po nieudanej akcji [authentifier-utilisateur] (nieuznane dane uwierzytelniające): wyświetlany jest wówczas formularz uwierzytelniający w celu wprowadzenia poprawek;
Teraz, gdy wiemy, w jakich momentach należy wyświetlić formularz uwierzytelniający, możemy obliczyć jego szablon w [ModelForAuthentificationView] (wiersz 12):
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForAuthentificationView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# dane strony są umieszczane w szablonie
modèle = {}
# stan aplikacji
état = résultat["état"]
# szablon zależy od stanu
if état == 700:
# przypadek wyświetlania pustego formularza
modèle["login"] = ""
# nie ma żadnego błędu do wyświetlenia
modèle["error"] = False
elif état == 201:
# nieprawidłowe uwierzytelnienie
# ponownie wyświetlany jest pierwotnie wprowadzony użytkownik
modèle["login"] = request.form.get("user")
# należy wyświetlić błąd
modèle["error"] = True
# lista komunikatów o błędach HTML
erreurs = ""
for erreur in résultat["réponse"]:
erreurs += f"<li>{erreur}</li>"
modèle["erreurs"] = erreurs
# zwracany jest szablon
return modèle
Komentarze
- wiersz 8: metoda [get_model_for_view] widoku uwierzytelniania musi dostarczyć słownik z trzema kluczami [error, erreurs, login]. Obliczenie to odbywa się na podstawie kodu stanu zwróconego przez kontroler akcji;
- wiersz 12: pobierany jest kod stanu zwrócony przez kontroler, który przetworzył bieżącą akcję;
- wiersze 14–29: model zależy od tego kodu stanu;
- wiersze 15–18: sytuacja, w której należy wyświetlić pusty formularz uwierzytelniający;
- wiersze 20–29: przypadek nieudanego uwierzytelnienia: wyświetla się identyfikator wprowadzony przez użytkownika oraz komunikat o błędzie. Użytkownik może wówczas ponownie spróbować uwierzytelnić się;
- wiersz 22: identyfikator pierwotnie wprowadzony przez użytkownika można odzyskać z żądania klienta;
- wiersz 24: sygnalizuje się, że istnieją błędy do wyświetlenia;
- wiersze 26–29: w przypadku błędu wynik [‘réponse’] zawiera listę błędów;
32.5.6. Generowanie odpowiedzi HTML
Wróćmy do modelu MVC aplikacji HTML:
- w 2 (2a, 2b): kontroler wykonuje akcję;
- w 3 (3a, 3b, 3c): wybierany jest widok i wysyłany do klienta;
W [3a] wybierany jest typ odpowiedzi (jSON, XML, HTML). Widzieliśmy już, jak generowane są odpowiedzi jSON i XML, ale nie omówiliśmy jeszcze odpowiedzi HTML. Są one generowane przez klasę [HtmlResponse]:

Przypomnijmy, w jaki sposób w głównym skrypcie [main] określa się rodzaj odpowiedzi, jaką należy przekazać użytkownikowi:
….
# tworzymy odpowiedź do wysłania
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# wysyłanie odpowiedzi
return response, status_code
gdzie w wierszu 3 config[‘responses’] to następujący słownik:
# różne typy odpowiedzi (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
Zatem to klasa [HtmlResponse] generuje odpowiedź HTML. Jej kod jest następujący:
# słownik odpowiedzi HTML w zależności od statusu zawartego w wyniku
from flask import make_response, render_template
from flask.wrappers import Response
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 zwracanego przez kontroler
état = résultat["état"]
# czy należy wykonać przekierowanie?
for redirection in config["redirections"]:
# stany wymagające przekierowania
états = redirection["états"]
if état in états:
# należy wykonać przekierowanie
return redirect(f"/{redirection['to']}"), status.HTTP_302_FOUND
# każdemu stanowi odpowiada jeden widok
# szukamy go na liście widoków
views_configs = config["views"]
trouvé = False
i = 0
# przeglądamy listę widoków
nb_views = len(views_configs)
while not trouvé and i < nb_views:
# widok nr i
view_config = views_configs[i]
# raporty powiązane z widokiem nr i
états = view_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 not trouvé:
# jeśli nie istnieje żaden widok dla bieżącego stanu aplikacji
# generuje widok błędów
view_config = config["view-erreurs"]
# obliczamy szablon widoku do wyświetlenia
model_for_view = view_config["model_for_view"]
modèle = model_for_view.get_model_for_view(request, session, config, résultat)
# generuje się kod odpowiedzi HTML
html = render_template(view_config["view_name"], modèle=modèle)
# tworzy się odpowiedź HTTP
response = make_response(html)
response.headers['Content-Type'] = 'text/html; charset=utf-8'
# zwracamy wynik
return response, status_code
- linia 11: metoda [build_http_response] odpowiedzialna za wygenerowanie odpowiedzi HTML otrzymuje następujące parametry:
- [request, session, dict]: są to parametry wykorzystywane przez kontroler do przetworzenia bieżącej akcji;
- [status_code, résultat] to dwa wyniki wygenerowane przez ten sam kontroler;
- wiersz 14: jak już wspomnieliśmy, odpowiedź serwera HTML zależy od kodu stanu zawartego w słowniku [résultat];
- wiersze 16–22: najpierw obsługuje się przekierowania. Na razie pominę ten przypadek, dopóki nie natrafimy na przykład przekierowania. Należy zauważyć, że przekierowania są typowym przypadkiem użycia serwera HTML. Nie spotykamy się z tym przypadkiem w przypadku serwerów jSON i ouXML;
- wiersze 24–41: przeszukujemy widoki w poszukiwaniu tego, którego lista [états] zawiera poszukiwany stan;
- wiersze 42–46: jeśli nie znaleziono żadnego widoku, oznacza to nieoczekiwany błąd. Weźmy przykład. Podczas normalnego działania aplikacji akcja [/supprimer-simulation] nigdy nie powinna powodować błędu. Zobaczymy bowiem, że usuwanie symulacji odbywa się na podstawie linków generowanych przez kod. Linki te są poprawne i nie mogą prowadzić do błędu. Jednak, jak widzieliśmy, użytkownik może bezpośrednio wpisać URL lub [/supprimer-simulation/id], powodując w ten sposób błąd. W takim przypadku kontroler [SupprimerSimulationController] zwraca kod stanu 601. Kod ten nie znajduje się jednak na liście kodów stanu powodujących wyświetlenie strony HTML. W związku z tym wyświetlony zostanie ekran błędu. Jest on zdefiniowany w konfiguracji w następujący sposób:
# podsumowanie nieoczekiwanych błędów
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
- wiersz 49: gdy znamy już widok, który ma zostać wyświetlony, pobieramy klasę generującą jego model. Znajduje się ona również w konfiguracji [config];
- wiersz 50: po znalezieniu tej klasy generujemy model widoku;
- wiersz 52: po obliczeniu modelu M widoku V można wygenerować kod HTML widoku;
- wiersze 54–55: tworzy się odpowiedź HTTP z treścią HTML;
- wiersze 56–57: zwracamy odpowiedź HTTP wraz z jej kodem statusu;
32.5.7. Testy [Postman]
Wykonamy zapytania generujące kody [700, 201], które wyświetlają ekran uwierzytelniania:
- [init-session-html-700]: 700 to kod stanu po pomyślnym wykonaniu akcji [init-session]: wyświetlany jest wówczas pusty formularz uwierzytelniający;
- [authentifier-utilisateur-201]: 201 to kod stanu po nieudanej akcji [authentifier-utilisateur] (nieuznane dane logowania): wyświetlany jest wówczas formularz uwierzytelniający w celu wprowadzenia poprawek;
Wystarczy ponownie je wykorzystać i sprawdzić, czy prawidłowo wyświetlają ekran uwierzytelniania. Poniżej przedstawiono dwa przypadki:
Przypadek 1: [init-session-html-700], rozpoczęcie sesji HTML;

Odpowiedź jest następująca:

- w [5] tryb [Preview] pozwala wyświetlić otrzymaną stronę HTML;
- w [6] widzimy oczekiwany pusty formularz;
- w [7] Postman nie kliknął linku do obrazu na stronie;
- w [8] tryb [Raw] zapewnia dostęp do otrzymanego HTML;

- w [3] – link, którego Postman nie załadował. Wyświetlił wartość atrybutu [alt=alternative], która pojawia się, gdy obraz nie może zostać załadowany. W tym przypadku wygląda raczej na to, że Postman nie chciał go załadować. Można to sprawdzić, wysyłając żądanie dotyczące URL i [http://localhost :5000/static/images.logo.jpg] za pomocą Postmana:
Przypadek 2: [authentifier-utilisateur-201], błędne uwierzytelnienie

Teraz spróbujmy przeprowadzić błędne uwierzytelnienie po pomyślnej inicjalizacji sesji HTML:

Powyżej:
- w [4,7]: żądanie wysyła ciąg znaków [user=bernard&password=thibault];
Odpowiedź jest następująca:

- w [4] wyświetlany jest komunikat o błędzie;
- w przypadku [3] ponownie wyświetlono nieprawidłowego użytkownika;
32.5.8. Wniosek
Udało nam się przetestować widok [vue-authentification.html] bez konieczności pisania pozostałych widoków. Było to możliwe, ponieważ:
- wszystkie kontrolery są już napisane;
- [Postman] pozwala nam wysyłać żądania do serwera bez konieczności posiadania wszystkich widoków. Podczas pisania kontrolerów należy być przygotowanym na obsługę żądań, których żaden widok nie obsługuje. Nigdy nie należy z góry zakładać, że „to żądanie jest niemożliwe”. Należy to sprawdzić;
32.6. Widok służący do obliczania podatku

32.6.1. Opis widoku
Widok służący do obliczania podatku wygląda następująco:

Widok składa się z trzech części:
- 1: górny pasek jest generowany przez fragment [v-bandeau.html], który został już przedstawiony;
- 2: formularz do obliczania podatku wygenerowany przez fragment [v-calcul-impot.html];
- 3: menu zawierające dwa linki, wygenerowane przez fragment [v-menu.html];
Widok obliczeń podatku jest generowany przez następujący kod [vue-calcul-impot.html]:
<!-- dokument HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Wymagane metatagi -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- baner -->
{% include "fragments/v-bandeau.html" %}
<!-- układ dwukolumnowy -->
<div class="row">
<!-- menu -->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- formularz obliczeniowy -->
<div class="col-md-9">
{% include "fragments/v-calcul-impot.html" %}
</div>
</div>
<!-- przypadek powodzenia -->
{% if modèle.success %}
<!-- wyświetla się komunikat o pomyślnym zakończeniu -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-success" role="alert">
{{modèle.impôt}}</br>
{{modèle.décôte}}</br>
{{modèle.réduction}}</br>
{{modèle.surcôte}}</br>
{{modèle.taux}}</br>
</div>
</div>
</div>
{% endif %}
{% if modèle.error %}
<!-- lista błędów w 9 kolumnach -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs | safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Komentarze
- komentujemy wyłącznie nowości, z którymi jeszcze się nie spotkaliśmy;
- wiersz 16: umieszczenie górnego paska widoku w pierwszym wierszu Bootstrap widoku;
- wiersz 21: umieszczenie menu, które zajmie trzy kolumny drugiego wiersza Bootstrap widoku (wiersze 18, 20);
- wiersz 25: umieszczenie formularza do obliczania podatku, który zajmie dziewięć kolumn (wiersz 24) w drugim wierszu Bootstrap widoku (wiersz 18);
- wiersze 30–46: jeśli obliczenie podatku zakończy się powodzeniem ([modèle.success=True]), wówczas wynik obliczenia podatku jest wyświetlany w zielonej ramce (wiersze 37–43). Ramka ta znajduje się w trzecim wierszu Bootstrap widoku (wiersz 32) i zajmuje dziewięć kolumn (wiersz 36) po prawej stronie trzech pustych kolumn (wiersze 33–35). Ramka ta będzie zatem znajdować się poniżej formularza obliczania podatku;
- wiersze 48–61: jeśli obliczenie podatku zakończy się niepowodzeniem ([modèle.error=True]), wówczas komunikat o błędzie zostanie wyświetlony w różowej ramce (wiersze 55–58). Ramka ta znajduje się w trzecim wierszu Bootstrap widoku (wiersz 50) i zajmuje dziewięć kolumn (wiersz 54) po prawej stronie trzech pustych kolumn (wiersze 51–53). Ramka ta będzie zatem również znajdować się poniżej formularza obliczania podatku;
32.6.2. Fragment [v-calcul-impot.html]
Fragment [v-calcul-impot.html] wyświetla formularz obliczania podatku w aplikacji internetowej:
Kod fragmentu [v-calcul-impot.html] jest następujący:

<!-- formularz HTML wysłany -->
<form method="post" action="/calculer-impot">
<!-- komunikat w 12 kolumnach na niebieskim tle -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
<!-- elementy formularza -->
<fieldset class="form-group">
<!-- pierwszy wiersz w 9 kolumnach -->
<div class="row">
<!-- napis w 4 kolumnach -->
<legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
<!-- przyciski opcji w 5 kolumnach-->
<div class="col-md-5">
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" {{modèle.checkedOui}}>
<label class="form-check-label" for="gridRadios1">
Oui
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" {{modèle.checkedNon}}>
<label class="form-check-label" for="gridRadios2">
Non
</label>
</div>
</div>
</div>
<!-- drugi wiersz w 9 kolumnach -->
<div class="form-group row">
<!-- opis w 4 kolumnach -->
<label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
<!-- pole do wprowadzania liczby dzieci w 5 kolumnach -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="{{modèle.enfants}}" required>
</div>
</div>
<!-- trzeci wiersz w 9 kolumnach -->
<div class="form-group row">
<!-- opis w 4 kolumnach -->
<label for="salaire" class="col-md-4 col-form-label">Salaire annuel net imposable</label>
<!-- pole do wprowadzania liczby dzieci w 5 kolumnach -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel net imposable" aria-describedby="salaireHelp" value="{{modèle.salaire}}" required>
<small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
</div>
</div>
<!-- czwarty wiersz, przycisk [submit] w 5 kolumnach -->
<div class="form-group row">
<div class="col-md-5">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Uwagi
- wiersz 2: formularz HTML zostanie przesłany (atrybut [method]) do URL [/calculer-impot] (atrybut [action]). Przesłane wartości będą odpowiadały wartościom z pól wprowadzania danych:
- wartość zaznaczonego przycisku opcji w postaci:
- [marié=oui], jeśli zaznaczono przycisk opcji [Oui] (wiersze 17–22). [marié] to wartość atrybutu [name] z wiersza 18, a [oui] to wartość atrybutu [value] z wiersza 18;
- [marié=non], jeśli przycisk opcji [Non] jest zaznaczony (wiersze 23–28). [marié] to wartość atrybutu [name] z wiersza 24, a [non] to wartość atrybutu [value] z wiersza 24;
- wartość pola wprowadzania liczbowego w wierszu 37 w postaci [enfants=xx], gdzie [enfants] jest wartością atrybutu [name] z wiersza 37, a [xx] to wartość wprowadzona przez użytkownika za pomocą klawiatury;
- wartość pola wprowadzania liczbowego w wierszu 46 w postaci [salaire=xx], gdzie [salaire] jest wartością atrybutu [name] z wiersza 46, a [xx] to wartość wprowadzona przez użytkownika za pomocą klawiatury;
Ostatecznie wartość zapisana w bazie danych będzie miała postać [marié=xx&enfants=yy&salaire=zz].
- (ciąg dalszy)
- wprowadzone wartości zostaną zapisane, gdy użytkownik kliknie przycisk typu [submit] w wierszu 53;
- wiersze 16–30: dwa przyciski opcji:
![]()
Oba przyciski opcji należą do tej samej grupy przycisków opcji, ponieważ mają ten sam atrybut [name] (wiersze 18, 24). Przeglądarka dba o to, aby w grupie przycisków opcji w danym momencie był zaznaczony tylko jeden. Dlatego kliknięcie jednego z nich powoduje odznaczenie tego, który był zaznaczony wcześniej;
- są to przyciski opcji ze względu na atrybut [type="radio"] (wiersze 18, 24);
- Po wyświetleniu formularza (przed wprowadzeniem danych) jeden z przycisków opcji musi być zaznaczony: w tym celu wystarczy dodać atrybut [checked=’checked’] do odpowiedniego tagu <input type="radio">. Odbywa się to za pomocą zmiennych dynamicznych:
- [modèle.checkedOui] w wierszu 18;
- [modèle->checkedNon] w wierszu 24;
Zmienne te będą częścią szablonu widoku.
- wiersz 37: pole wprowadzania liczbowego [type="number"] z minimalną wartością 0 [min="0"]. W nowszych przeglądarkach oznacza to, że użytkownik będzie mógł wprowadzić jedynie liczbę >=0. W tych samych nowszych przeglądarkach wprowadzanie danych może odbywać się za pomocą suwaka, który można przesuwać w górę lub w dół. Atrybut [step="1"] w wierszu 37 wskazuje, że suwak będzie działał z krokami co 1 jednostkę. W rezultacie suwak przyjmie wyłącznie wartości całkowite w zakresie od 0 do n z krokiem równym 1. W przypadku ręcznego wprowadzania danych oznacza to, że liczby z przecinkiem nie będą akceptowane;
- wiersz 37: w niektórych widokach pole wprowadzania danych potomnych powinno być wstępnie wypełnione ostatnią wartością wprowadzoną w tym polu. W tym celu stosuje się atrybut [value], który ustala wartość do wyświetlenia w polu wprowadzania danych. Wartość ta będzie dynamiczna i generowana przez zmienną [modèle.enfants];
- wiersz 37: atrybut [required] wymusza na użytkowniku wprowadzenie danych, aby formularz został zatwierdzony;
- wiersz 46: te same wyjaśnienia dotyczące wprowadzania wynagrodzenia, co w przypadku danych dotyczących dzieci;
- wiersz 53: przycisk typu [submit], który uruchamia POST z wprowadzonymi wartościami do URL [/calculer-impot] (wiersz 2);
![]()
32.6.3. Fragment [v-menu.html]
Ten fragment wyświetla menu po lewej stronie formularza obliczania podatku:

Kod tego fragmentu jest następujący:
<!-- menu Bootstrap -->
<nav class="nav flex-column">
<!-- wyświetlanie listy linków HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{optionMenu.url}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
Komentarze
- wiersze 2–7: tag HTML [nav] otacza fragment dokumentu HTML zawierający linki nawigacyjne do innych dokumentów;
- wiersz 5: znacznik HTML [a] wprowadza link nawigacyjny:
- [optionMenu.url]: jest to URL, do którego następuje przejście po kliknięciu linku [optionMenu.text]. Jest to zatem operacja [GET optionMenu.url] wykonywana przez przeglądarkę. [optionMenu.url] będzie absolutną ścieżką URL mierzoną od katalogu głównego aplikacji [http://machine :port/chemin]. Tak więc w [1] utworzymy link:
- wiersz 5: szablon fragmentu o nazwie [modèle.optionsMenu] będzie listą o następującej postaci:
- wiersze 2, 7: klasy CSS i [nav, flex-column, nav-link] to klasy Bootstrap, które określają wygląd menu;
32.6.4. Test wizualny
Gromadzimy te różne elementy w folderze [Tests] i tworzymy szablon testowy dla widoku [vue-calcul-impot.html]:

Skrypt testowy [test_vue_calcul_impot] będzie wyglądał następująco:
from flask import Flask, render_template, make_response
# aplikacja Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Strona główna URL
@app.route('/')
def index():
# dane strony są umieszczane w szablonie
modèle = {}
# formularz
modèle["checkedOui"] = ""
modèle["checkedNon"] = 'checked="checked"'
modèle["enfants"] = 2
modèle["salaire"] = 300000
# komunikat o pomyślnym zakończeniu
modèle["success"] = True
modèle["impôt"] = "Montant de l'impôt : 1000 euros"
modèle["décôte"] = "Décôte : 15 euros"
modèle["réduction"] = "Réduction : 20 euros"
modèle["surcôte"] = "Surcôte : 0 euros"
modèle["taux"] = "Taux d'imposition : 14 %"
# komunikat o błędzie
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
# tworzy się listę HTML błędów
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# menu
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# wyświetlanie strony
return make_response(render_template("views/vue-calcul-impot.html", modèle=modèle))
# strona główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Komentarze
- wiersze 9–34: inicjujemy wszystkie dynamiczne części widoku [vue-calcul-impot.html] oraz fragmentów [v-calcul-impot.html] i [v-menu.html];
- wiersz 36: wyświetla się widok [vue-calcul-impot.html];
Po uruchomieniu skryptu testowego [test_vue_calcul_impot] otrzymujemy następujący wynik:
Pracujemy nad tym widokiem, aż uzyskany efekt wizualny będzie dla nas satysfakcjonujący. Następnie możemy przejść do integracji widoku z tworzoną aplikacją internetową.

32.6.5. Obliczanie modelu widoku
Po ustaleniu wyglądu widoku można przystąpić do obliczenia modelu widoku w rzeczywistych warunkach. Przypomnijmy kody stanów, które prowadzą do tego widoku. Znajdują się one w pliku konfiguracyjnym:
{
# widok obliczeń podatku
"états": [
# /uwierzytelnianie-użytkownika pomyślne
200,
# /obliczyć-podatek pomyślnie
300,
# /obliczyć-podatek – niepowodzenie
301,
# /wyświetlić-obliczenia-podatku
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
Są to zatem kody stanu [200, 300, 301, 800], które powodują wyświetlenie widoku obliczania podatku. Aby poznać znaczenie tych kodów, można skorzystać z testów [Postman] przeprowadzonych na aplikacji jSON:
- [authentifier-utilisateur-200]: 200 to kod stanu po pomyślnym zakończeniu operacji [authentifier-utilisateur]: wyświetlany jest wówczas pusty formularz obliczania podatku;
- [calculer-impot-300]: 300 to kod stanu po pomyślnym zakończeniu operacji [calculer-impot]. Wyświetlany jest wówczas formularz obliczeniowy z wprowadzonymi danymi oraz kwotą podatku. Użytkownik może wówczas wykonać kolejne obliczenie;
- kod stanu [301] jest kodem uzyskanym w przypadku błędnego obliczenia podatku;
- kod statusu [800] zostanie przedstawiony w dalszej części. Nie spotkaliśmy się z nim jeszcze;
Teraz, gdy wiemy, w jakich momentach powinien być wyświetlany formularz obliczania podatku, możemy opracować jego szablon w klasie [ModelForCalculImpotView]:

from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForCalculImpotView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# dane z widoku są umieszczane w szablonie
modèle = {}
# stan aplikacji
état = résultat["état"]
# szablon zależy od stanu
if état in [200, 800]:
# początkowe wyświetlenie pustego formularza
modèle["success"] = False
modèle["error"] = False
modèle["checkedNon"] = 'checked="checked"'
modèle["checkedOui"] = ""
modèle["enfants"] = ""
modèle["salaire"] = ""
elif état == 300:
# pomyślne wykonanie obliczeń – wyświetlenie wyniku
modèle["success"] = True
modèle["error"] = False
modèle["impôt"] = f"Montant de l'impôt : {résultat['réponse']['impôt']} euros"
modèle["décôte"] = f'Décôte : {résultat["réponse"]["décôte"]} euros'
modèle["réduction"] = f"Réduction : {résultat['réponse']['réduction']} euros"
modèle["surcôte"] = f'Surcôte : {résultat["réponse"]["surcôte"]} euros'
modèle["taux"] = f"Taux d'imposition : {résultat['réponse']['taux'] * 100} %"
# formularz przywrócony z wprowadzonymi wartościami
modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
modèle["enfants"] = request.form.get("enfants")
modèle["salaire"] = request.form.get("salaire")
elif état == 301:
# wystąpił błąd – formularz przywrócony z wprowadzonymi wartościami
modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
modèle["enfants"] = request.form.get("enfants")
modèle["salaire"] = request.form.get("salaire")
# błąd
modèle["success"] = False
modèle["error"] = True
modèle["erreurs"] = ""
for erreur in résultat['réponse']:
modèle['erreurs'] += f"<li>{erreur}</li>"
# opcje menu
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# przywracamy szablon
return modèle
Uwagi
- wiersz 12: widok, który ma zostać wyświetlony, zależy od kodu stanu zwracanego przez kontroler;
- wiersze 14–21: wyświetlenie pustego formularza;
- wiersze 22–35: przypadek pomyślnego obliczenia podatku. Ponownie wyświetlane są wprowadzone wartości oraz kwota podatku;
- wiersze 36–47: przypadek nieudanego obliczenia podatku;
- wiersze 49–52: obliczenie dwóch opcji z menu;
32.6.6. Testy [Postman]
Inicjujemy sesję HTML za pomocą zapytania [init-session-html-700], a następnie uwierzytelniamy się za pomocą zapytania [authentifier-utilisateur-200]. Następnie używamy następującego zapytania [calculer-impot-300]:
Odpowiedź serwera jest następująca:


Teraz wypróbujmy następujące żądanie: [calculer-impot-301]:

Odpowiedź serwera jest następująca:
Teraz spróbujmy rozważyć nieoczekiwany przypadek, w którym w zapytaniu POST brakuje parametrów. Taki przypadek nie jest możliwy podczas normalnego działania aplikacji. Jednak każdy może „sklecić” zapytanie HTTP, tak jak to właśnie robimy:


- w [6] odznaczyliśmy parametr wysyłany w [marié];
Odpowiedź serwera jest następująca:

- na [3], komunikat o błędzie serwera;
W tej aplikacji mieliśmy wybór. Mogliśmy przypisać temu przypadkowi błędu kod stanu, który przekierowuje do strony z nieoczekiwanymi błędami. W tej aplikacji wybraliśmy dla każdego kontrolera dwa kody stanu:
- [xx0]: w przypadku powodzenia;
- [xx1]: w przypadku niepowodzenia;
W przypadku niepowodzeń można zróżnicować kody stanu, aby uzyskać bardziej precyzyjne zarządzanie błędami. Moglibyśmy na przykład zastosować:
- [xx1]: dla błędów, które mają być wyświetlane na stronie, na której wystąpił błąd;
- [xx2]: dla błędów nieoczekiwanych podczas normalnego użytkowania aplikacji;
32.7. Widok listy symulacji

32.7.1. Opis widoku
Widok zawierający listę symulacji wygląda następująco:

Widok wygenerowany przez kod [vue-liste-simulations.html] składa się z trzech części:
- 1: górny pasek jest generowany przez fragment kodu [v-bandeau.html], który został już przedstawiony;
- 3: tabela symulacji generowana przez fragment [v-liste-simulations.html];
- 2: menu zawierające dwa linki, wygenerowane przez fragment kodu [v-menu.html], który został już przedstawiony;
Widok symulacji jest generowany przez następujący kod [vue-liste-simulations.html]:
<!-- dokument HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Wymagane metatagi -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- pasek nagłówkowy -->
{% include "fragments/v-bandeau.html" %}
<!-- układ dwukolumnowy -->
<div class="row">
<!-- menu w trzech kolumnach-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- lista symulacji w 9 kolumnach-->
<div class="col-md-9">
{% include "fragments/v-liste-simulations.html" %}
</div>
</div>
</div>
</body>
</html>
Komentarze
- wiersz 16: dołączenie paska nagłówkowego aplikacji [1];
- wiersz 21: dołączenie menu [2]. Zostanie ono wyświetlone w trzech kolumnach pod paskiem nagłówkowym;
- wiersz 26: wstawienie tabeli symulacji [3]. Zostanie ona wyświetlona w dziewięciu kolumnach pod paskiem nagłówkowym i po prawej stronie menu;
Omówiliśmy już dwa z trzech fragmentów tego widoku:
- [v-bandeau.html]: w akapicie dotyczącym linku;
- [v-menu.html]: w akapicie z linkiem;
Fragment [v-liste-simulations.html] wygląda następująco:
{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- komunikat na niebieskim tle -->
<div class="alert alert-primary" role="alert">
<h4>Votre liste de simulations est vide</h4>
</div>
{% endif %}
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
<!-- komunikat na niebieskim tle -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- tabela symulacji -->
<table class="table table-sm table-hover table-striped">
<!-- nagłówki sześciu kolumn tabeli -->
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Marié</th>
<th scope="col">Nombre d'enfants</th>
<th scope="col">Salaire annuel</th>
<th scope="col">Montant impôt</th>
<th scope="col">Surcôte</th>
<th scope="col">Décôte</th>
<th scope="col">Réduction</th>
<th scope="col">Taux</th>
<th scope="col"></th>
</tr>
</thead>
<!-- treść tabeli (wyświetlane dane) -->
<tbody>
<!-- wyświetlanie poszczególnych symulacji poprzez przeglądanie tabeli symulacji -->
{% for simulation in modèle.simulations %}
<!-- wyświetlanie wiersza tabeli z 6 kolumnami – tag <tr> -->
<!-- kolumna 1: nagłówek wiersza (nr symulacji) – tag <th scope='row' -->
<!-- kolumna 2: wartość parametru [marié] – tag <td> -->
<!-- kolumna 3: wartość parametru [enfants] – tag <td> -->
<!-- kolumna 4: wartość parametru [salaire] – tag <td> -->
<!-- kolumna 5: wartość parametru [impôt] (podatku) – tag <td> -->
<!-- kolumna 6: wartość parametru [surcôte] – tag <td> -->
<!-- kolumna 7: wartość parametru [décôte] – tag <td> -->
<!-- kolumna 8: wartość parametru [réduction] – tag <td> -->
<!-- kolumna 9: wartość parametru [taux] (podatku) – tag <td> -->
<!-- kolumna 10: link do usunięcia symulacji – tag <td> -->
<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="/supprimer-simulation/{{simulation.id}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
Komentarze
- tabela HTML została utworzona za pomocą tagu <table> (wiersze 15 i 62);
- nagłówki kolumn tabeli znajdują się wewnątrz tagu <thead> (nagłówek tabeli, wiersze 17, 30). Tag <tr> (wiersz tabeli, wiersze 18 i 29) wyznacza wiersz. W wierszach 19–28 tag <th> (nagłówek tabeli) definiuje nagłówek kolumny. Jest ich więc dziesięć. [scope="col"] oznacza, że nagłówek odnosi się do kolumny. [scope="row"] oznacza, że nagłówek odnosi się do wiersza;
- wiersze 32–61: znacznik <tbody> otacza dane wyświetlane w tabeli;
- wiersze 47–58: tag <tr> otacza wiersz tabeli;
- wiersz 48: tag <th scope=’row’> definiuje nagłówek wiersza. Przeglądarka wyróżnia ten nagłówek;
- wiersze 49–57: każdy tag <td> (dane tabeli) definiuje kolumnę wiersza;
- wiersz 34: lista symulacji znajduje się w szablonie [modèle.simulations], który jest listą słowników;
- wiersz 57: link do usunięcia symulacji. Model URL wykorzystuje numer symulacji wyświetlanej w danym wierszu;
32.7.2. Test wizualny
Tworzymy skrypt testowy dla widoku [vue-liste-simulations.html]:
Skrypt [test_vue_liste_simulations] wygląda następująco:

from flask import Flask, make_response, render_template
# aplikacja Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Strona główna URL
@app.route('/')
def index():
# dane strony są umieszczane w szablonie
modèle = {}
# przekształcamy symulacje do formatu wymaganego przez stronę
modèle["simulations"] = [
{
"id": 7,
"marié": "oui",
"enfants": 2,
"salaire": 60000,
"impôt": 448,
"décôte": 100,
"réduction": 20,
"surcôte": 0,
"taux": 0.14
},
{
"id": 19,
"marié": "non",
"enfants": 2,
"salaire": 200000,
"impôt": 25600,
"décôte": 0,
"réduction": 0,
"surcôte": 8400,
"taux": 0.45
}
]
# menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
# wyświetlanie strony
return make_response(render_template("views/vue-liste-simulations.html", modèle=modèle))
# strona główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Komentarze
- wiersze 12–35: umieszczamy dwie symulacje w modelu
- wiersze 37–39: tabela opcji menu;
Wyświetlmy ten widok, uruchamiając ten skrypt. Otrzymujemy następujący wynik:

Pracujemy nad tym widokiem, aż uzyskany efekt wizualny będzie nas satysfakcjonował. Następnie możemy przejść do integracji widoku z tworzoną aplikacją internetową.
32.7.3. Obliczanie modelu widoku
Po ustaleniu wyglądu widoku można przystąpić do obliczenia modelu widoku w rzeczywistych warunkach. Przypomnijmy kody stanów, które prowadzą do tego widoku. Znajdują się one w pliku konfiguracyjnym:

{
# widok listy symulacji
"états": [
# /wyświetl-listę-symulacji
500,
# /usuń-symulację
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
Są to zatem kody stanu [500, 600], które powodują wyświetlenie widoku symulacji. Aby poznać znaczenie tych kodów, można skorzystać z testów [Postman] przeprowadzonych w aplikacji jSON:
- [lister-simulations-500]: 500 to kod stanu po pomyślnym wykonaniu akcji [lister-simulations]: wyświetlana jest wówczas lista symulacji przeprowadzonych przez użytkownika;
- [supprimer-simulation-600]: 600 to kod stanu po pomyślnym zakończeniu akcji [supprimer-simulation]. Wyświetlana jest wówczas nowa lista symulacji uzyskana po tym usunięciu;
Teraz, gdy wiemy, w jakich momentach należy wyświetlić listę symulacji, możemy obliczyć jej model w klasie [ModelForListeSimulationsView]:
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForListeSimulationsView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# dane strony są umieszczane w szablonie
modèle = {}
# symulacje znajdują się w odpowiedzi kontrolera, który wykonał akcję
# w postaci tablicy słowników TaxPayer
modèle["simulations"] = résultat["réponse"]
# menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
# zwracamy szablon
return modèle
Komentarze
- wiersz 13: symulacje do wyświetlenia znajdują się w [wynik["réponse"]];
- wiersze 15–17: opcje menu do wyświetlenia;
32.7.4. Testy [Postman]
Inicjuje
- inicjuje sesję HTML;
- przeprowadza się uwierzytelnienie;
- wykonuje trzy obliczenia podatku;
Test [lister-simulations-500] pozwala uzyskać kod stanu 500. Odpowiada on żądaniu wyświetlenia symulacji:

Odpowiedź serwera jest następująca:

Test [supprimer-simulation-600] pozwala nam uzyskać kod stanu 600. W tym przypadku usuniemy symulację nr 2.
Zwrócony wynik to lista symulacji, z której usunięto jedną symulację:


32.8. Widok nieoczekiwanych błędów
W tym przypadku jako nieoczekiwany błąd określamy błąd, który nie powinien wystąpić podczas normalnego korzystania z aplikacji internetowej. Na przykład próba obliczenia podatku bez uwierzytelnienia. Nic nie uniemożliwia użytkownikowi bezpośredniego wpisania kodu URL [/calcul-impot] w przeglądarce. Ponadto, jak widzieliśmy, może on wprowadzić ciąg znaków „POST” w polu „URL [/calcul-impot]”, nie wysyłając oczekiwanych parametrów. Widzieliśmy, że nasza aplikacja internetowa potrafiła poprawnie odpowiedzieć na to żądanie. Błąd nieoczekiwany to taki, który nie powinien wystąpić w ramach aplikacji HTML. Jeśli jednak się pojawi, oznacza to prawdopodobnie, że ktoś próbuje „zhakować” aplikację. Ze względów dydaktycznych postanowiliśmy wyświetlać stronę błędów w takich przypadkach. W rzeczywistości moglibyśmy ponownie wyświetlić ostatnią stronę wysłaną do klienta. Wystarczy w tym celu zapisać w sesji ostatnią wysłaną odpowiedź HTML. W przypadku nieoczekiwanego błędu zwracamy tę odpowiedź. Dzięki temu użytkownik będzie miał wrażenie, że serwer nie reaguje na jego błędy, ponieważ wyświetlana strona nie ulega zmianie.
32.8.1. Prezentacja widoku

Widok przedstawiający nieoczekiwane błędy wygląda następująco:

Widok wygenerowany przez kod [vue-erreurs.html] składa się z trzech części:
- 1: górny pasek jest generowany przez fragment kodu [v-bandeau.html], który został już przedstawiony;
- 2: nieoczekiwany błąd lub błędy;
- 3: menu zawierające trzy linki, wygenerowane przez fragment [v-menu.html], który już został przedstawiony;
Widok nieoczekiwanych błędów jest generowany przez następujący skrypt [vue-erreurs.html]:
<!-- dokument HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Wymagane metatagi -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- pasek z 12 kolumnami -->
{% include "fragments/v-bandeau.html" %}
<!-- wiersz z dwiema sekcjami -->
<div class="row">
<!-- menu w 3 kolumnach-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- lista błędów w 9 kolumnach -->
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs inattendues suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
</div>
</body>
</html>
Komentarze
- wiersz 16: dołączenie paska aplikacji [1];
- wiersz 21: dołączenie menu [3]. Zostanie ono wyświetlone w trzech kolumnach pod paskiem nagłówkowym;
- wiersze 24–29: wyświetlenie obszaru błędów w dziewięciu kolumnach;
- wiersz 25: wyświetlanie to nastąpi w ramce Bootstrap na różowym tle;
- wiersz 26: tekst wprowadzający;
- wiersz 27: tag <ul> otacza listę punktowaną. Lista ta pochodzi z szablonu [modèle.erreurs];
Omówiliśmy już dwa fragmenty tego widoku:
32.8.2. Test wizualny
Tworzymy skrypt testowy dla widoku [vue-erreurs.html]:

from flask import Flask, render_template, make_response
# aplikacja Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Strona główna URL
@app.route('/')
def index():
# dane strony są umieszczane w szablonie
modèle = {}
# tworzymy listę HTML błędów
content = ""
for erreur in ["erreur1", "erreur2"]:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# opcje menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/calculer-impot'},
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# wyświetlanie strony
return make_response(render_template("views/vue-erreurs.html", modèle=modèle))
# strona główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Komentarze
- wiersze 11–15: tworzenie listy błędów HTML;
- wiersze 17–20: tablica opcji menu;
Uruchommy ten skrypt. Otrzymujemy następujący wynik:
Pracujemy nad tym widokiem, aż uzyskany efekt wizualny będzie nas satysfakcjonował. Następnie możemy przejść do integracji widoku z tworzoną aplikacją internetową.

32.8.3. Obliczanie modelu widoku

Po ustaleniu wyglądu widoku można przystąpić do obliczenia modelu widoku w rzeczywistych warunkach. Przypomnijmy kody stanów, które prowadzą do tego widoku. Znajdują się one w pliku konfiguracyjnym:
# widoki HTML i ich szablony zależą od stanu zwracanego przez kontroler
"views": [
{
# widok uwierzytelniania
"états": [
# /pomyślne rozpoczęcie sesji
700,
# /zakończenie-sesji
400,
# /uwierzytelnianie-użytkownika – niepowodzenie
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# widok obliczeń podatku
"états": [
# /uwierzytelnianie użytkownika zakończone sukcesem
200,
# /obliczyć-podatek pomyślnie
300,
# /obliczyć-podatek nieudane
301,
# /wyświetl-obliczenia-podatku
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# widok listy symulacji
"états": [
# /wyświetl-listę-symulacji
500,
# /usuń-symulację
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# widok nieoczekiwanych błędów
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
To właśnie kody stanów, które nie prowadzą do widoku HTML z wierszy 3–41, powodują wyświetlenie widoku nieoczekiwanych błędów.
Obliczenia modelu widoku [vue-erreurs.html] są wykonywane przez następującą klasę [ModelForErreursView]:
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForErreursView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# model
modèle = {}
# błędy
modèle["erreurs"] = ""
for erreur in résultat['réponse']:
modèle['erreurs'] += f"<li>{erreur}</li>"
# menu
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# generowanie modelu
return modèle
Komentarze
- wiersze 11–14: obliczanie modelu [modèle.erreurs] używanego przez widok [vue-erreurs.html];
- wiersze 16–197: obliczanie szablonu [modèle.optionsMenu] używanego przez fragment [v-menu.html];
32.8.4. Testy [Postman]
Wykonujemy:
- akcja [/init-session/html];
- a następnie akcję [/init-session/x];
Odpowiedź HTML wygląda wówczas następująco:

32.9. Implementacja akcji z menu aplikacji
W tym miejscu omówimy implementację akcji z menu. Przypomnijmy znaczenie linków, z którymi się spotkaliśmy
Widok | Link | Cel | Rola |
Obliczanie podatku | [Liste des simulations] | [/lister-simulations] | Poproś o listę symulacji |
[Fin de session] | [/fin-session] | ||
Lista symulacji | [Calcul de l’impôt] | [/afficher-calcul-impot] | Wyświetl widok obliczeń podatku |
[Fin de session] | [/fin-session] | ||
Nieoczekiwane błędy | [Calcul de l’impôt] | [/afficher-calcul-impot] | Wyświetl widok obliczeń podatku |
[Liste des simulations] | [/lister-simulations] | ||
[Fin de session] | [/fin-session] |
Należy pamiętać, że kliknięcie linku powoduje wywołanie akcji GET prowadzącej do miejsca docelowego linku. Akcje [/lister-simulations, /fin-session] zostały zaimplementowane za pomocą operacji GET, co pozwala nam ustawić je jako miejsca docelowe linków. Gdy akcja jest realizowana za pomocą POST, użycie linku nie jest już możliwe, chyba że zostanie on powiązany z kodem JavaScript.
32.9.1. Akcja [/afficher-calcul-impot]
Z powyższych działań wynika, że działanie [/afficher-calcul-impot] nie zostało jeszcze wdrożone. Jest to operacja nawigacji między dwoma widokami: serwer jSON ani XML nie mają powodu, by ją wdrożyć, ponieważ nie obsługują pojęcia widoku. To serwer HTML wprowadza to pojęcie.
Musimy zatem zaimplementować akcję [/afficher-calcul-impot]. Pozwoli nam to przeanalizować sposób implementacji akcji w serwerze.
Najpierw musimy dodać nowy kontroler pomocniczy. Nazwiemy go [AfficherCalculImpotController]:

Ten kontroler należy dodać do pliku konfiguracyjnego [config]:
# kontrolery
from AfficherCalculImpotController import AfficherCalculImpotController
from AuthentifierUtilisateurController import AuthentifierUtilisateurController
from CalculerImpotController import CalculerImpotController
from CalculerImpotsController import CalculerImpotsController
from FinSessionController import FinSessionController
from GetAdminDataController import GetAdminDataController
…
# dozwolone działania i ich kontrolery
"controllers": {
# inicjowanie sesji obliczeniowej
"init-session": InitSessionController(),
# uwierzytelnianie użytkownika
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# obliczanie 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(),
# wyświetlenie widoku obliczeń podatku
"afficher-calcul-impot": AfficherCalculImpotController(),
# pobieranie danych z urzędu skarbowego
"get-admindata": GetAdminDataController(),
# główny kontroler
"main-controller": MainController()
},
…
# widoki HTML i ich szablony zależą od stanu zwracanego przez kontroler
"views": [
{
# widok uwierzytelniania
…
},
{
# widok obliczania podatku
"états": [
# /uwierzytelnianie-użytkownika pomyślne
200,
# /obliczyć-podatek pomyślnie
300,
# /oblicz-podatek – niepowodzenie
301,
# /wyświetlić-obliczenia-podatku
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{…
}
],
- wiersz 2: nowy kontroler;
- wiersz 28: nowa akcja i jej kontroler;
- wiersz 51: nowy kontroler zwróci kod stanu 800. Podczas zmiany widoku nie może wystąpić żaden błąd. Wyświetlany widok to widok [vue-calcul-impot.html], który przeanalizowaliśmy, wyjaśniliśmy i przetestowaliśmy;
Kontroler [AfficherCalculImpotController] będzie wyglądał następująco:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class AfficherCalculImpotController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# pobieranie elementów ścieżki
dummy, action = request.path.split('/')
# zmiana widoku – wystarczy ustawić kod stanu
return {"action": action, "état": 800, "réponse": ""}, status.HTTP_200_OK
Uwagi
- wiersz 6: podobnie jak inne kontrolery pomocnicze, nowy kontroler implementuje interfejs [InterfaceController];
- wiersz 13: zmiany widoku są łatwe do zaimplementowania: wystarczy zwrócić kod stanu powiązany z widokiem docelowym, w tym przypadku kod 800, jak wspomniano powyżej;
32.9.2. Akcja [/fin-session]
Akcja [/fin-session] ma szczególny charakter. Nie prowadzi ona bezpośrednio do widoku, lecz do przekierowania. Przypomnijmy, że przekierowania są konfigurowane w konfiguracji [config] w następujący sposób:
# przekierowania
"redirections": [
{
"états": [
400, # /pomyślne zakończenie sesji
],
# przekierowanie do
"to": "/init-session/html",
}
],
W aplikacji występuje tylko jedno przekierowanie:
- gdy kontroler zwraca kod stanu [400] (wiersz 5), należy przekierować klienta do URL [http://machine:port/chemin/init-session/html] (wiersz 8);
Kod stanu [400] jest kodem zwracanym po pomyślnym wykonaniu akcji [/fin-session]. Dlaczego zatem należy przekierować klienta do URL lub [/init-session/html]? Ponieważ kod akcji [/fin-session] usuwa typ sesji obecny w sesji internetowej. Nie wiadomo już wtedy, że trwa sesja HTML. Należy ją przekierować. Robi się to za pomocą akcji [/init-session/html].
Przekierowania HTML są obsługiwane przez klasę [HtmlResponse]:
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"]
# czy należy wykonać przekierowanie?
for redirection in config["redirections"]:
# stany wymagające przekierowania
états = redirection["états"]
if état in états:
# należy wykonać przekierowanie
return redirect(f"{redirection['to']}"), status.HTTP_302_FOUND
# każdemu stanowi odpowiada jeden widok
# szukamy go na liście widoków
..
- wiersze 6–12 obsługują przekierowania;
- wiersz 7: config[‘redirections’] to lista przekierowań. Każde przekierowanie jest słownikiem zawierającym następujące klucze:
- [états]: stany zwracane przez kontroler, które prowadzą do przekierowania;
- [to]: adres przekierowania;
- wiersze 7–12: przeglądamy listę przekierowań;
- wiersz 9: dla każdego przekierowania pobierane są stany, które do niego prowadzą;
- wiersz 10: jeśli sprawdzany stan znajduje się na tej liście, wówczas wykonuje się przekierowanie, wiersz 12;
- wiersz 12: przypominamy, że metoda [build_http_response] musi zwracać krotkę zawierającą dwa elementy:
- [response]: odpowiedź HTTP, którą należy wygenerować. Jest ona tworzona za pomocą funkcji [redirect], której parametrem jest adres przekierowania;
- [status_code]: kod statusu odpowiedzi HTTP, w tym przypadku kod [status.HTTP_302_FOUND], który informuje klienta, że musi zostać przekierowany;
Przeprowadźmy test [Postman]. Wykonujemy:
- inicjujemy sesję HTML [init-session/html];
- uwierzytelniamy się: [/authentifier-utilisateur];
- kończymy sesję [/fin-session];

Odpowiedź serwera brzmi następująco:

Uzyskano stronę uwierzytelniającą. To właśnie ta, na którą czekaliśmy. Zobaczmy teraz, w jaki sposób została uzyskana. Przejdźmy do konsoli [Postman] (Ctrl-Alt-C):

- w [1], akcja [/fin-session];
- na [2-3], kod statusu 302 HTTP zwrócony przez serwer informuje klienta, że nastąpiło przekierowanie;
- w [4] klient [Postman] podąża za przekierowaniem;
32.10. Testy aplikacji HTML w rzeczywistych warunkach
Kod został napisany, a każda akcja przetestowana przy użyciu [Postman]. Pozostaje nam przetestować sekwencję widoków w rzeczywistych warunkach. Potrzebujemy sposobu na zainicjowanie sesji HTML. Wiemy, że należy wysłać do serwera żądanie [/init-session/html]. Nie jest to zbyt praktyczne rozwiązanie. Wolelibyśmy rozpocząć od URL [/].
W głównym skrypcie [main] zapisaliśmy następującą ścieżkę:
from flask import request, Flask, session, url_for, redirect
…
…
@app.route('/', methods=['GET'])
def index() -> tuple:
# przekierowanie do /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
…
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
- wiersze 4–7: obsługa ścieżki [/]. Punktem wejścia do aplikacji internetowej będzie URL[/init-session/html] (wiersz 10). Również w wierszu 7 przekierowujemy klienta do tej ścieżki URL:
- funkcja [url_for] jest importowana w wierszu 1. Posiada ona tutaj dwa parametry (wiersz 7):
- pierwszym parametrem jest nazwa jednej z funkcji routingu, w tym przypadku tej z wiersza 11. Widać, że funkcja ta oczekuje parametru [type_response], który określa typ odpowiedzi (json, xml, html) oczekiwany przez klienta;
- drugi parametr przejmuje nazwę parametru z wiersza 11, [type_response], i nadaje mu wartość. Gdyby istniały inne parametry, operację tę powtórzono by dla każdego z nich;
- funkcja ta zwraca URL powiązany z funkcją wskazaną przez dwa podane jej parametry. W tym przypadku będzie to URL z wiersza 10, gdzie parametr zostaje zastąpiony swoją wartością [/init-session/html];
- funkcja [redirect] została zaimportowana w wierszu 1. Jej zadaniem jest wysłanie do klienta nagłówka przekierowania HTTP:
- pierwszy parametr to URL, do którego klient ma zostać przekierowany;
- drugim parametrem jest kod statusu odpowiedzi HTTP wysłanej do klienta. Kod [status.HTTP_302_FOUND] odpowiada przekierowaniu HTTP;
Jesteśmy gotowi. Przedstawiamy teraz kilka sekwencji widoków.
W przeglądarce włączamy śledzenie żądań (F12 w przeglądarkach Chrome, Firefox, Edge) i wysyłamy żądanie startowe URL do [http://localhost:5000/]. Odpowiedź serwera jest następująca:

Jeśli przyjrzymy się wymianie danych sieciowych, która miała miejsce między klientem a serwerem:

- widać, że w [4, 5] przeglądarka otrzymała żądanie przekierowania do URL [/init-session/html];
Wypełnijmy otrzymany formularz;

Następnie przeprowadźmy kilka symulacji:


Poprośmy o listę symulacji:

Usuńmy pierwszą symulację:

Zakończmy sesję:

Zachęcamy czytelnika do przeprowadzenia dalszych testów.