Шаблоны
Django имеет встроенную систему для работы с шаблонами - Django template language (DTL). С помощью этой системы можно использовать в HTML файлах различные переменные и теги, например, циклы.
Создадим папку templates
в нашем приложении flights, в ней создадим ещё одну папку flights
, и туда будем помещать
все наши шаблоны.
<!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, с помощью которого будет проще кастомизировать наш сайт. Также в
В заголовке страницы создадим блок title - {% block title %}Авиаперелёты{% endblock %}
. С помощью блоков шаблон-наследник
сможет переопределять значения в них. Собственно, в данном случае, если ребёнок не будет переопределять значение блока
title, то заголовок страницы будет "Авиаперелёты".
Также создадим пустой block css
на случай, если наследнику нужно будет спользовать свои стили.
В теле страницы с помощью bootstrap5 создадим навигационное меню. Внутри меню поместим условие
{% if user.is_authenticated %}
, с помощью которого будет проверять, авторизован ли пользователь.
Если авторизован, в меню появится ссылка на просмотр броней и кнопка выхода с фамилией и именем пользователя.
Если не авторизован, то в меню появится кнопка входа.
Ранее мы прописали названия для всех маршрутов сайта, поэтому в шаблонах можем не хардкодить адрес страницы, а использовать
тег {% url "название маршрута" %}
.
Так как мы используем в наших представлениях встроенный фреймворк messages, нам нужно как-то выводить их на страницах.
Для этого поместим в условие {% if messages %}
контейнер, в котором проитерируемся по всем сообщениям и для каждого
создадим ещё один контейнер нужного класса (ранее в настройках мы заменили теги сообщений, чтобы они соответствовали классам
bootstrap), в котором поместим текст сообщения и кнопку для закрытия.
В конце создадим пустой block content
, в котором наследники будут создавать контент своей страницы.
{% 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
.
{% 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 %}
Аналогично странице регистрации создаём страницу входа.
{% 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 %}
, чтобы сообщить об этом пользователю.
{% 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
,
чтобы перейти на страницу с дополнительной информацией о рейсе.
В зависимости от активной вкладки будем выводить либо кнопку удаления брони, либо кнопку для оставления отзыва.
{% 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
выводим содержимое формы, которую передали в шаблон.
{% 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 %}
Итерируемся по всем броням, привязанным к выбранному рейсу и на основе этой информации создаём таблицу со всеми пассажирами.
Также итерируемся по всем отзывывам и выводим их под таблицей.