Skip to content

36. Ćwiczenie praktyczne: wersja 16

Image

36.1. Wprowadzenie

Akcje URL w naszej aplikacji mają obecnie postać [/action/param1/param2/…]. Chcielibyśmy móc dodawać przedrostki do tych URL. Na przykład z przedrostkiem [/do] mielibyśmy URL o postaci [/do/action/param1/param2/…].

36.2. Nowa konfiguracja tras

Image

  • na [2] spowoduje zmianę sposobu obliczania tras;
  • w pliku [1] plik [config] zostaje zmodyfikowany w celu uwzględnienia tej zmiany;

Konfiguracja [config] przyjmuje następujący kształt:


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))

    # konfiguracja tras
    import routes
    routes.configure(config)

    # przywracamy konfigurację
    return config
  • wiersze 37–39: moduł [routes] (wiersz 38) odpowiada za konfigurację tras (wiersz 39);

Plik tras bez tokenu CSRF [configs/routes_without_csrftoken] zmienia się w następujący sposób:


# zależności

from flask import redirect, request, session, url_for
from flask_api import status

# konfiguracja aplikacji
config = {}

# kontroler front-end
def front_controller() -> tuple:
    # przekazujemy żądanie do kontrolera głównego
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# katalog główny aplikacji
def index() -> tuple:
    # przekierowanie do /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

# init-session
def init_session(type_response: str) -> tuple:
    # uruchamia się kontroler powiązany z akcją
    return front_controller()

# uwierzytelnianie-użytkownika
def authentifier_utilisateur() -> tuple:
    # uruchamia się kontroler powiązany z akcją
    return front_controller()

# oblicz-podatek
def calculer_impot() -> tuple:
    # uruchamia się kontroler powiązany z akcją
    return front_controller()


Z pliku usunięto trasy. Pozostały jedynie funkcje z nimi powiązane.

Pliki tras z tokenami CSRF i [configs/routes_with_csrftoken] ulegają tej samej zmianie:


# zależności

from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf

# konfiguracja
config = {}

# kontroler frontowy
def front_controller() -> tuple:
    # żądanie jest przekazywane do kontrolera głównego
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# katalog główny aplikacji
def index() -> tuple:
    # przekierowanie do /init-session/html
    return redirect(url_for("init_session", type_response="html"csrf_token=generate_csrf()), status.HTTP_302_FOUND)

# init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
    # uruchamia się kontroler powiązany z akcją
    return front_controller()

# uwierzytelnianie użytkownika
def authentifier_utilisateur(csrf_token: str) -> tuple:
    # uruchamiany jest kontroler powiązany z akcją
    return front_controller()



# init-session-without-csrftoken dla klientów json i xml
def init_session_without_csrftoken(type_response: str) -> tuple:
    # przekierowanie do /init-session/type_response
    return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
                    status.HTTP_302_FOUND)

Trasy są obliczane przez następujący moduł [configs/routes]:


from flask import Flask

def configure(config: dict):
    # konfiguracja tras

    # aplikacja Flask
    app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
    config['app'] = app

    # import tras aplikacji internetowej
    if config['parameters']['with_csrftoken']:
        import routes_with_csrftoken as routes
    else:
        import routes_without_csrftoken as routes

    # wstrzykiwanie konfiguracji do tras
    routes.config = config

    # prefiks URL aplikacji
    prefix_url = config["parameters"]["prefix_url"]

    # token CSRF
    with_csrftoken = config["parameters"]['with_csrftoken']
    if with_csrftoken:
        csrftoken_param = f"/<string:csrf_token>"
    else:
        csrftoken_param = ""

    # trasy aplikacji Flask
    # katalog główny aplikacji
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

    # init-session
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

    # init-session-without-csrftoken
    if with_csrftoken:
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken/<string:type_response>',
                         methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)

    # uwierzytelnianie-użytkownika
    app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
                     view_func=routes.authentifier_utilisateur)

    # oblicz-podatek
    app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impot)

    # obliczanie podatku w trybie wsadowym
    app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impots)

    # wyświetlenie listy symulacji
    app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.lister_simulations)

    # usunięcie symulacji
    app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
                     view_func=routes.supprimer_simulation)

    # zakończenie sesji
    app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
                     view_func=routes.fin_session)

    # wyświetl-obliczenia-podatku
    app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_calcul_impot)

    # pobierz-dane-administracyjne
    app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
                     view_func=routes.get_admindata)

    # wyświetl-widok-obliczeń-podatkowych
    app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_calcul_impot)

    # wyświetl-widok-uwierzytelniania
    app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_authentification)

    # wyświetl-widok-lista-symulacji
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_simulations)

    # wyświetl-widok-lista_błędów
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_erreurs)

    # przypadek szczególny
    if with_csrftoken:
        # init-session-without-csrftoken dla klientów json i xml
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)
  • wiersze 6–8: tworzona jest aplikacja Flask i umieszczana w konfiguracji;
  • wiersze 10–14: importowany jest plik tras odpowiedni do danej sytuacji. Należy pamiętać, że importowany plik w rzeczywistości nie zawiera żadnych tras. Zawiera jedynie funkcje z nimi powiązane;
  • wiersz 17: funkcje powiązane z trasami muszą znać konfigurację aplikacji;
  • wiersz 20: odnotowujemy prefiks URL. Może on być pusty;
  • wiersze 22–27: trasy z tokenem CSRF mają jeden parametr więcej niż te, które go nie posiadają. Aby uwzględnić tę różnicę, używa się zmiennej [csrftoken_param]:
    • zawiera pusty ciąg znaków, jeśli w trasach nie ma tokenu CSRF;
    • zawiera ciąg znaków [/<string:csrf_token>], jeśli występuje token CSRF;
  • wiersze 29–96: każda trasa w aplikacji jest powiązana z funkcją z pliku tras zaimportowanego w wierszach 10–14;

36.3. Nowe kontrolery

Image

W poprzedniej wersji wszystkie kontrolery uzyskiwały aktualnie przetwarzaną akcję w następujący sposób:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # pobieranie elementów ścieżki
        params = request.path.split('/')
        action = params[1]

Ten kod nie działa już, jeśli występuje prefiks, na przykład [/do/lister-simulations]. W takim przypadku akcja w wierszu 3 brzmiałaby [do] i byłaby zatem nieprawidłowa.

Kod ten przekształca się w następujący sposób:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # pobieranie elementów ścieżki
        prefix_url = config["parameters"]["prefix_url"]
        params = request.path[len(prefix_url):].split('/')
        action = params[1]

Należy to zrobić we wszystkich kontrolerach.

36.4. Nowe szablony

Image

W poprzedniej wersji każda klasa szablonu generowała szablon w następujący sposób:


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
        

        # token CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # możliwe działania z poziomu widoku
        modèle['actions_possibles'] = []

        # wyświetlanie szablonu
        return modèle

Od teraz szablon będzie miał dodatkowy klucz – prefiks URL:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # dane strony są enkapsulowane w szablonie
        modèle = {}
        # stan aplikacji
        état = résultat["état"]
        # szablon zależy od stanu
        

        # token CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # możliwe akcje z poziomu widoku
        modèle['actions_possibles'] = []
    
        # prefiks URL
        modèle["prefix_url"] = config["parameters"]["prefix_url"]

        # zwracamy szablon
        return modèle

36.5. Nowe fragmenty

Image

Wszystkie fragmenty zawierające URL należy zmodyfikować.

Fragment [v-authentification]


<!-- formularz HTML – wysyłamy jego wartości za pomocą akcji [authentifier-utilisateur] -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">

    <!-- tytuł -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>


</form>

Fragment [v-calcul-impot]


<!-- formularz HTML wysłany -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
    <!-- komunikat w 12 kolumnach na niebieskim tle -->
    

</form>

Fragment [v-liste-simulations]




{% if modèle.simulations is defined and modèle.simulations|length!=0 %}


<!-- tabela symulacji -->
<table class="table table-sm table-hover table-striped">
    
    <tr>
        <th scope="row">{{simulation.id}}</th>
        <td>{{simulation.marié}}</td>
        <td>{{simulation.enfants}}</td>
        <td>{{simulation.salaire}}</td>
        <td>{{simulation.impôt}}</td>
        <td>{{simulation.surcôte}}</td>
        <td>{{simulation.décôte}}</td>
        <td>{{simulation.réduction}}</td>
        <td>{{simulation.taux}}</td>
        <td><a href="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
    </tr>
    {% endfor %}
    </tr>
    </tbody>
</table>
{% endif %}

Fragment [v-menu]


<!-- menu Bootstrap -->
<nav class="nav flex-column">
    <!-- wyświetlanie listy linków HTML -->
    {% for optionMenu in modèle.optionsMenu %}
    <a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
    {% endfor %}
</nav>

36.6. Nowa odpowiedź HTML

Image

W poprzedniej wersji kod odpowiedzi HTML kończył się przekierowaniem:


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"]

        

        # 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

Kod ten wygląda teraz następująco:


def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        

        # teraz należy wygenerować kod przekierowania URL, nie zapominając o tokenie CSRF, jeśli zostanie on wymagany
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # odpowiedź przekierowania
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

36.7. Testy

Image

Umieśćmy następujący prefiks w pliku konfiguracyjnym [parameters]:



        # token CSRF
        "with_csrftoken"True,
        # obsługiwane bazy danych: MySQL (MySQL), PostgreSQL (PostgreSQL)
        "databases"["mysql""pgres"],
        # prefiks URL aplikacji
        # ustaw pusty ciąg znaków, jeśli nie chcesz prefiksu, w przeciwnym razie /prefiks
        "prefix_url""/do",

Uruchommy aplikację, a następnie wywołajmy URL [http://localhost:5000/do]. Odpowiedź jest następująca:

Image

  • w [1] – prefiks URL;
  • w przypadku [2] – token CSRF;

Aplikację można również przetestować za pomocą testów konsolowych dla [http-clients/09]:

Image

W plikach konfiguracyjnych [1] i [2] prefiks z pliku URL należy umieścić w pliku URL na serwerze:


        "server": {
            # „urlServer”: „http://127.0.0.1:5000”,
            "urlServer""http://127.0.0.1:5000/do",
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                
            }
  • wiersz 3: kod serwera URL zawiera teraz prefiks kodu URL;

Po wprowadzeniu tej zmiany wszystkie testy konsoli powinny działać poprawnie.