Skip to content

36. Практичне завдання: версія 16

Image

36.1. Вступ

URL у нашому додатку наразі мають вигляд [/action/param1/param2/…]. Ми хотіли б мати можливість додавати префікс до цих URL. Наприклад, із префіксом [/do] ми отримаємо URL у форматі [/do/action/param1/param2/…].

36.2. Нова конфігурація маршрутів

Image

  • на [2], розрахунок маршрутів буде змінено;
  • у [1] файл [config] змінюється, щоб відобразити цю зміну;

Конфігурація [config] набуває такого вигляду:


def configure(config: dict) -> dict:
    # конфігурація syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # параметри додатка
    import parameters
    config['parameters'] = parameters.configure(config)

    # конфігурація бази даних
    import database
    config["database"] = database.configure(config)

    # інстанціювання рівнів додатка
    import layers
    config['layers'] = layers.configure(config)

    # налаштування MVC шару [web]
    config['mvc'] = {}

    # конфігурація контролерів шару [web]
    import controllers
    config['mvc'].update(controllers.configure(config))

    # дії ASV (Action Show View)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    # дії ADS (Action Do Something)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    # налаштування відповідей HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    # налаштування маршрутів
    import routes
    routes.configure(config)

    # застосовуємо налаштування
    return config
  • рядки 37–39: модуль [routes] (рядок 38) відповідає за конфігурацію маршрутів (рядок 39);

Файл маршрутів без токенів CSRF [configs/routes_without_csrftoken] змінюється наступним чином:


# залежності

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

# конфігурація додатка
config = {}

# фронт-контролер
def front_controller() -> tuple:
    # запит перенаправляється до головного контролера
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# кореневий каталог додатка
def index() -> tuple:
    # перенаправлення на /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:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# аутентифікація користувача
def authentifier_utilisateur() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# розрахувати-податок
def calculer_impot() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()


З файлу видалено маршрути. Залишилися лише функції, пов’язані з ними.

Файли маршрутів із маркерами CSRF та [configs/routes_with_csrftoken] зазнають тієї самої долі:


# залежності

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

# конфігурація
config = {}

# фронт-контролер
def front_controller() -> tuple:
    # запит перенаправляється до головного контролера
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# кореневий каталог додатка
def index() -> tuple:
    # перенаправлення на /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:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# аутентифікація користувача
def authentifier_utilisateur(csrf_token: str) -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()



# init-session-without-csrftoken для клієнтів json та xml
def init_session_without_csrftoken(type_response: str) -> tuple:
    # перенаправлення на /init-session/type_response
    return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
                    status.HTTP_302_FOUND)

Маршрути обчислюються наступним модулем [configs/routes]:


from flask import Flask

def configure(config: dict):
    # налаштування маршрутів

    # додаток Flask
    app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
    config['app'] = app

    # імпорт маршрутів веб-додатку
    if config['parameters']['with_csrftoken']:
        import routes_with_csrftoken as routes
    else:
        import routes_without_csrftoken as routes

    # вбудовуємо конфігурацію в маршрути
    routes.config = config

    # префікс URL додатка
    prefix_url = config["parameters"]["prefix_url"]

    # токен CSRF
    with_csrftoken = config["parameters"]['with_csrftoken']
    if with_csrftoken:
        csrftoken_param = f"/<string:csrf_token>"
    else:
        csrftoken_param = ""

    # маршрути додатка Flask
    # кореневий каталог додатка
    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)

    # аутентифікація-користувача
    app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
                     view_func=routes.authentifier_utilisateur)

    # розрахунок-податку
    app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impot)

    # пакетний розрахунок податку
    app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impots)

    # перелік-симуляцій
    app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.lister_simulations)

    # видалити-симуляцію
    app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
                     view_func=routes.supprimer_simulation)

    # завершення сесії
    app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
                     view_func=routes.fin_session)

    # перегляд розрахунку податку
    app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_calcul_impot)

    # отримати-дані-адміністратора
    app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
                     view_func=routes.get_admindata)

    # перегляд-розрахунку-податків
    app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_calcul_impot)

    # перегляд-аутентифікації
    app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_authentification)

    # перегляд-переліку-симуляцій
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_simulations)

    # показати-перелік-помилок
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_erreurs)

    # особливий випадок
    if with_csrftoken:
        # ініціалізація сесії без csrftoken для клієнтів json та xml
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)
  • рядки 6–8: створюється додаток Flask і додається до конфігурації;
  • рядки 10–14: імпортується файл маршрутів, що відповідає даній ситуації. Слід пам’ятати, що імпортований файл насправді не містить маршрутів. Він містить лише функції, пов’язані з ними;
  • рядок 17: функції, пов’язані з маршрутами, потребують інформації про конфігурацію додатка;
  • рядок 20: звертаємо увагу на префікс URL. Він може бути порожнім;
  • рядки 22–27: маршрути з токеном CSRF мають додатковий параметр порівняно з тими, що його не мають. Для обробки цієї відмінності використовується змінна [csrftoken_param]:
    • вона містить порожній рядок, якщо в маршрутах немає маркера CSRF;
    • вона містить рядок [/<string:csrf_token>], якщо є маркер CSRF;
  • рядки 29–96: кожен маршрут додатка пов’язаний із функцією з файлу маршрутів, імпортованого в рядках 10–14;

36.3. Нові контролери

Image

У попередній версії всі контролери отримували дію, що обробляється, таким чином:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # отримання елементів шляху
        params = request.path.split('/')
        action = params[1]

Цей код більше не працює, якщо є префікс, наприклад [/do/lister-simulations]. У цьому випадку дія, рядок 3, буде [do] і, отже, буде неправильною.

Цей код перетворюється наступним чином:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # отримання елементів шляху
        prefix_url = config["parameters"]["prefix_url"]
        params = request.path[len(prefix_url):].split('/')
        action = params[1]

Це робимо у всіх контролерах.

36.4. Нові шаблони

Image

У попередній версії кожен клас шаблону генерував шаблон таким чином:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # інкапсуляція даних сторінки в шаблон
        modèle = {}
        # стан додатка
        état = résultat["état"]
        # шаблон залежить від стану
        

        # токен CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # можливі дії з рівня подання
        modèle['actions_possibles'] = []

        # виводимо шаблон
        return modèle

Відтепер шаблон матиме додатковий ключ — префікс URL:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # інкапсулюємо дані сторінки в модель
        modèle = {}
        # стан додатка
        état = résultat["état"]
        # модель залежить від стану
        

        # токен CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # можливі дії з рівня подання
        modèle['actions_possibles'] = []
    
        # префікс URL
        modèle["prefix_url"] = config["parameters"]["prefix_url"]

        # повертаємо шаблон
        return modèle

36.5. Нові фрагменти

Image

Усі фрагменти, що містять URL, потрібно змінити.

Фрагмент [v-authentification]


<!-- форма HTML — відправляємо її значення за допомогою дії [authentifier-utilisateur] -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">

    <!-- заголовок -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>


</form>

Фрагмент [v-calcul-impot]


<!-- форма HTML, відправлена -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
    <!-- повідомлення у 12 стовпчиках на синьому тлі -->
    

</form>

Фрагмент [v-liste-simulations]




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


<!-- таблиця моделювань -->
<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 %}

Фрагмент [v-menu]


<!-- меню Bootstrap -->
<nav class="nav flex-column">
    <!-- відображення списку посилань 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. Нова відповідь HTML

Image

У попередній версії код відповіді HTML закінчувався перенаправленням:


class HtmlResponse(InterfaceResponse):

    def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        # відповідь HTML залежить від коду стану, повернутого контролером
        état = résultat["état"]

        

        # тепер потрібно згенерувати URL перенаправлення, не забуваючи про токен CSRF, якщо його вимагають
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # відповідь на перенаправлення
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

Тепер цей код виглядає так:


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

        # тепер потрібно згенерувати URL для перенаправлення, не забуваючи про токен CSRF, якщо його вимагають
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # відповідь перенаправлення
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

36.7. Тестування

Image

Додамо такий префікс у файл конфігурації [parameters]:



        # токен CSRF
        "with_csrftoken"True,
        # керовані бази даних MySQL (mysql), PostgreSQL (pgres)
        "databases"["mysql""pgres"],
        # префікс URL додатка
        # встановити порожній рядок, якщо префікс не потрібен, або /префікс в іншому випадку
        "prefix_url""/do",

Запустимо додаток, а потім надішлімо запит URL [http://localhost:5000/do], відповідь буде такою:

Image

  • у [1] — префікс URL;
  • у [2] — токен CSRF;

Додаток також можна протестувати за допомогою консольних тестів [http-clients/09]:

Image

У файлах конфігурації [1] та [2] префікс URL має бути включений до URL сервера:


        "server": {
            # «urlServer»: «http://127.0.0.1:5000»,
            "urlServer""http://127.0.0.1:5000/do",
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                
            }
  • рядок 3: URL сервера тепер містить префікс URL;

Після внесення цієї зміни всі тести консолі повинні працювати.