Перейти к содержанию

Шаблоны

Django имеет встроенную систему для работы с шаблонами - Django template language (DTL). С помощью этой системы можно использовать в HTML файлах различные переменные и теги, например, циклы.

Создадим папку templates в нашем приложении flights, в ней создадим ещё одну папку flights, и туда будем помещать все наши шаблоны.

base.html
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}Авиаперелёты{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <style>{% block css %}{% endblock %}</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light px-3">
    <a class="navbar-brand" href="{% url "view_flights" %}">Авиаперелёты</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
            {% if user.is_authenticated %}
            <li class="nav-item">
                <a class="nav-link" href="{% url "view_bookings" "upcoming" %}">Мои рейсы</a>
            </li>
            {% endif %}
        </ul>

        <ul class="navbar-nav ms-auto">
            {% if user.is_authenticated %}
                <li class="nav-item">
                    <a class="nav-link" href="{% url "logout" %}">Выход ({{ user.first_name }} {{ user.last_name }})</a>
                </li>
            {% else %}
                <li class="nav-item">
                    <a class="nav-link" href="{% url "login" %}">Войти</a>
                </li>
            {% endif %}
        </ul>
    </div>
</nav>
{% if messages %}
    <div class="messages">
        {% for message in messages %}
            <div class="alert {% if message.tags %}{{ message.tags }}{% endif %} alert-dismissible fade show" role="alert">
                {{ message }}
                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Закрыть"></button>
            </div>
        {% endfor %}
    </div>
{% endif %}
<main class="container mt-3">
    {% block content %}
    {% endblock %}
</main>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>

Чтобы не повторять один и тот же HTML код во всех шаблонах, создадим базовый шаблон, от которого потом будем наследоваться с помощью тега {% extends %}.

В <head> подключим css файл bootstrap5, с помощью которого будет проще кастомизировать наш сайт. Также в нужно подключить js скрипт bootstrap5.

В заголовке страницы создадим блок title - {% block title %}Авиаперелёты{% endblock %}. С помощью блоков шаблон-наследник сможет переопределять значения в них. Собственно, в данном случае, если ребёнок не будет переопределять значение блока title, то заголовок страницы будет "Авиаперелёты".

Также создадим пустой block css на случай, если наследнику нужно будет спользовать свои стили.

В теле страницы с помощью bootstrap5 создадим навигационное меню. Внутри меню поместим условие {% if user.is_authenticated %}, с помощью которого будет проверять, авторизован ли пользователь.
Если авторизован, в меню появится ссылка на просмотр броней и кнопка выхода с фамилией и именем пользователя. Если не авторизован, то в меню появится кнопка входа.

Ранее мы прописали названия для всех маршрутов сайта, поэтому в шаблонах можем не хардкодить адрес страницы, а использовать тег {% url "название маршрута" %}.

Так как мы используем в наших представлениях встроенный фреймворк messages, нам нужно как-то выводить их на страницах. Для этого поместим в условие {% if messages %} контейнер, в котором проитерируемся по всем сообщениям и для каждого создадим ещё один контейнер нужного класса (ранее в настройках мы заменили теги сообщений, чтобы они соответствовали классам bootstrap), в котором поместим текст сообщения и кнопку для закрытия.

В конце создадим пустой block content, в котором наследники будут создавать контент своей страницы.

register.html
{% extends "flights/base.html" %}
{% load django_bootstrap5 %}

{% block title %}Регистрация{% endblock %}

{% block content %}
    <div class="container py-5">
        <h2 class="mb-3">Регистрация</h2>
        <form method="POST">
            {% csrf_token %}
            {% bootstrap_form register_form %}
            {% bootstrap_button button_type="submit" content="Зарегистрироваться" %}
        </form>
        <p class="mt-3">
            Уже зарегистрированы? <a href="{% url "login" %}">Войти</a>
        </p>
    </div>
{% endblock %}

Наследуем базовый шаблон с помощью тега {% extends "flights/base.html" %}.
С помощью тега {% load django_bootstrap5 %} подключим библиотеку django-bootstrap5, которую установили ранее.

Переопределим блок title и содержимое страницы поместим в блок content.

Создадим форму, в которой обязательно поместим тег {% csrf_token %} (POST запросы в Django защищены от межсайтовой подделки запросов).
Само содержимое формы создадим с помощью тега {% bootstrap_form register_form %} (register_form - форма, которую мы передали в шаблон). Также с помощью bootstrap создадим кнопку для отправки запроса.

Если вдруг пользователь уже зарегистрирован, оставим ссылку на представление login.

Регистрация
Отображение в браузере

Регистрация с ошибками
Ошибки валидации

login.html
{% extends "flights/base.html" %}
{% load django_bootstrap5 %}

{% block title %}Вход{% endblock %}

{% block content %}
    <div class="container py-5">
        <h2 class="mb-3">Вход</h2>
        <form method="POST">
            {% csrf_token %}
            {% bootstrap_form login_form %}
            {% bootstrap_button button_type="submit" content="Войти" %}
        </form>
        <p class="mt-3">
            Ещё не зарегистрированы? <a href="{% url "register" %}">Регистрация</a>
        </p>
    </div>
{% endblock %}

Аналогично странице регистрации создаём страницу входа.

Вход
Отображение в браузере

Регистрация с ошибками
Ошибки валидации

flights_list.html
{% extends "flights/base.html" %}

{% block content %}
    <h2 class="mb-3">Авиарейсы</h2>
    <table class="table">
        <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">Авиакомпания</th>
            <th scope="col">Тип</th>
            <th scope="col">Гейт</th>
            <th scope="col">Время отправления</th>
            <th scope="col">Забронировать</th>
        </tr>
        </thead>
        <tbody>
        {% for flight in flights %}
            <tr class="align-middle">
                <th scope="row">{{ flight.id }}</th>
                <td>{{ flight.airline }}</td>
                <td>{{ flight.get_type_display }}</td>
                <td>{{ flight.gate }}</td>
                <td>{{ flight.date }}</td>
                <td>
                    <a href="{% url "book_flight" flight.id %}" class="btn btn-primary">Забронировать</a>
                </td>
            </tr>
        {% empty %}
            <tr><td colspan="8">Нет доступных рейсов.</td></tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

Создаём таблицу. Итерируемся по всем объектам в переданном в шаблон списке flights и выводим необходимую информацию. Также в каждой строчке добавляем ссылку в виде кнопки на представление book_flight и передаём туда id рейса. Нажатие этой кнопки забронирует рейс.

Если список пустой, в DTL есть тег {% empty %}, чтобы сообщить об этом пользователю.

Просмотр рейсов
Отображение в браузере

my_flights.html
{% extends "flights/base.html" %}

{% block title %}Мои рейсы{% endblock %}

{% block content %}
    <h2 class="mb-3">Мои рейсы</h2>

    <ul class="nav nav-tabs">
        <li class="nav-item">
            <a class="nav-link {% if active_tab == "upcoming" %}active{% endif %}" href="{% url "view_bookings" "upcoming" %}">Предстоящие рейсы</a>
        </li>
        <li class="nav-item">
            <a class="nav-link {% if active_tab == "past" %}active{% endif %}" href="{% url "view_bookings" "past" %}">Прошедшие рейсы</a>
        </li>
    </ul>

    <table class="table">
        <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">Авиакомпания</th>
            <th scope="col">Тип</th>
            <th scope="col">Гейт</th>
            <th scope="col">Время отправления</th>
            <th scope="col">Зарегистрирован?</th>
            <th scope="col">Номер билета</th>
            <th scope="col">Действие</th>
        </tr>
        </thead>
        <tbody>
        {% for booking in bookings %}
            <tr class="align-middle">
                <th scope="row"><a href="{% url "flight_details" booking.flight.id %}">{{ booking.flight.id }}</a></th>
                <td>{{ booking.flight.airline }}</td>
                <td>{{ booking.flight.get_type_display }}</td>
                <td>{{ booking.flight.gate }}</td>
                <td>{{ booking.flight.date }}</td>
                <td>{{ booking.registered|yesno:"✅,❌" }}</td>
                <td>{% if booking.ticket %}{{ booking.ticket }}{% endif %}</td>
                {% if active_tab == "upcoming" %}
                    <td>
                        <a href="{% url "delete_booking" booking.id %}" class="btn btn-danger">Удалить бронь</a>
                    </td>
                {% else %}
                    <td>
                        <a href="{% url "give_feedback" booking.flight.id %}" class="btn btn-primary">Оставить отзыв</a>
                    </td>
                {% endif %}
            </tr>
        {% empty %}
            <tr><td colspan="8">Нет рейсов.</td></tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

Создадим навигационные вкладки с помощью bootstrap и поместим в них ссылки на то же представление, но с разным значеним активной вкладки. Активную вкладку будем подсвечивать.

Так же создадим таблицу с информацией о рейсах. В столбце с номерами рейсов поместим ссылки на представление flight_details, чтобы перейти на страницу с дополнительной информацией о рейсе.
В зависимости от активной вкладки будем выводить либо кнопку удаления брони, либо кнопку для оставления отзыва.

Предстоящие рейсы
Предстоящие рейсы

Прошедшие рейсы
Прошедшие рейсы

feedback.html
{% extends "flights/base.html" %}
{% load django_bootstrap5 %}

{% block title %}Отзыв{% endblock %}

{% block content %}
    <h2>Оставить отзыв</h2>
    <form method="post">
        {% csrf_token %}
        {% bootstrap_form form %}
        {% bootstrap_button button_type="submit" content="Отправить" %}
    </form>
{% endblock %}

С помощью библиотеки django_bootstrap5 выводим содержимое формы, которую передали в шаблон.

Написание отзыва
Отображение в браузере

Написание отзыва с ошибками
Ошибки валидации

flight_details.html
{% extends "flights/base.html" %}

{% block title %}Информация о рейсе{% endblock %}

{% block content %}
    <h1>Пассажиры на {{ flight }}</h1>

    <table class="table">
        <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">Фамилия и имя</th>
            <th scope="col">Зарегистрирован?</th>
            <th scope="col">Номер билета</th>
        </tr>
        </thead>
        <tbody>
        {% for booking in flight.bookings.all %}
            <tr>
                <th scope="row">{{ forloop.counter }}</th>
                <td>{{ booking.user.last_name }} {{ booking.user.first_name }}</td>
                <td>{{ booking.registered|yesno:"✅,❌" }}</td>
                <td>{% if booking.ticket %}{{ booking.ticket }}{% endif %}</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>

    <h2>Отзывы на рейс</h2>

    {% for feedback in flight.feedbacks.all %}
        <div class="card mb-3">
            <div class="card-body">
                <h5 class="card-title">
                    {{ feedback.user.last_name }} {{ feedback.user.first_name }} (Рейтинг: {{ feedback.rating }})
                    <small class="text-muted">{{ feedback.date }}</small>
                </h5>
                <p class="card-text">{{ feedback.text }}</p>
            </div>
        </div>
    {% empty %}
        <p>Пока нет отзывов</p>
    {% endfor %}
{% endblock %}

Итерируемся по всем броням, привязанным к выбранному рейсу и на основе этой информации создаём таблицу со всеми пассажирами.

Также итерируемся по всем отзывывам и выводим их под таблицей.

Информация о рейсе
Отображение в браузере