Развитие Интернета привело к тому, что грань между сайтами и мобильными приложениями размылась. И те и другие позволяют пользователям различными способами взаимодействовать с данными. К счастью, вы можете использовать Django для создания единого проекта, помогающего обслуживать как динамический сайт, так и группу мобильных приложений. Django представляет собой популярный веб-фреймворк Python — набор средств, упрощающих создание интерактивных веб-приложений. В этой главе вы с помощью Django создадите проект «Журнал обучения» — сетевую журнальную систему для отслеживания информации, полученной по определенной теме.
Мы напишем спецификацию для этого проекта, а затем определим модели для данных, с которыми будет работать приложение. Мы воспользуемся административной системой Django для ввода некоторых начальных данных, а затем научимся писать представления и шаблоны, на базе которых Django будет создавать страницы нашего сайта.
Django может реагировать на запросы страниц, упрощает чтение и запись данных в базы, управление действиями пользователей и многие другие операции. В главах 19 и 20 мы доработаем «Журнал обучения», а затем развернем его на рабочем сервере, чтобы вы (и любой другой пользователь) могли пользоваться этим журналом.
Приступая к работе над таким значимым проектом, как веб-приложение, необходимо сначала описать цели проекта в спецификации. Четко определившись с целями, можно начинать работу над управляемыми задачами, чтобы достичь этих целей.
В этом разделе мы напишем спецификацию для проекта «Журнал обучения» и начнем работать над первой фазой проекта. Она будет состоять из настройки виртуальной среды и формирования начальных аспектов проекта Django.
В полной спецификации описываются цели проекта, его функциональность, а также внешний вид и интерфейс пользователя. Как и любой хороший проект или бизнес-план, спецификация должна сосредоточиться на самых важных аспектах и обеспечивать планомерную разработку проекта. Здесь мы не будем писать полную спецификацию, а сформулируем несколько четких целей, которые будут задавать направление процесса разработки. Вот как выглядит спецификация.
Мы напишем веб-приложение «Журнал обучения», с помощью которого пользователь сможет вести журнал интересующих его тем и создавать в нем записи во время изучения каждой темы. Главная страница «Журнала обучения» содержит описание сайта и приглашает пользователя зарегистрироваться либо ввести свои учетные данные. После успешного входа пользователь получает возможность создавать новые темы, добавлять новые записи, читать и редактировать существующие записи.
Во время изучения нового материала бывает полезно вести журнал того, что вы узнали, — записи пригодятся для контроля и возвращения к необходимой информации. Это особенно актуально при изучении технических тем. Хорошее приложение повышает эффективность этого процесса.
Для работы с Django необходимо сначала создать виртуальную среду для работы. Виртуальная среда (virtual environment) представляет собой подраздел системы, в котором вы можете устанавливать пакеты изолированно от всех остальных пакетов Python. Отделение библиотек одного проекта от других проектов принесет пользу при развертывании приложения «Журнал обучения» на сервере в главе 20.
Создайте для проекта новый каталог learning_log, перейдите в этот каталог в терминальном режиме и создайте виртуальную среду, используя следующие команды:
learning_log$ python -m venv ll_env
learning_log$
Команда запускает модуль виртуальной среды venv и использует его для создания виртуальной среды ll_env (обратите внимание: в имени ll_env две буквы l, а не одна). Если для запуска программ или установки пакетов вы используете другую команду (например, python3), то подставьте ее на место python.
После того как виртуальная среда будет создана, ее необходимо активировать с помощью этой команды:
learning_log$ source ll_env/bin/activate
(ll_env)learning_log$
Команда запускает сценарий activate из каталога ll_env/bin. Когда среда активируется, ее имя выводится в круглых скобках; теперь вы можете устанавливать пакеты в среде и использовать те пакеты, которые были загружены ранее. Пакеты, установленные в среде ll_env, будут доступны только в то время, пока она остается активной.
ПРИМЕЧАНИЕ
Если вы работаете в системе Windows, то используйте команду ll_env\Scripts\activate (без слова source) для активизации виртуальной среды. Если используете PowerShell, то слово Activate должно начинаться с прописной буквы.
Чтобы завершить использование виртуальной среды, введите команду deactivate:
(ll_env)learning_log$ deactivate
learning_log$
Среда деактивируется и при закрытии терминального окна, в котором она работает.
После того как вы создали свою виртуальную среду и активизировали ее, установите Django с помощью инструмента pip:
(ll_env)learning_log$ pip install --upgrade pip
(ll_env)learning_log$ pip install django
Collecting django
--пропуск--
Installing collected packages: sqlparse, asgiref, django
Successfully installed asgiref-3.5.2 django-4.1 sqlparse-0.4.2
(ll_env)learning_log$
Поскольку pip скачивает ресурсы из различных источников, он обновляется довольно часто. Рекомендуется выполнять обновление pip каждый раз, когда вы создаете новую виртуальную среду.
Вы работаете в виртуальной среде, поэтому команда для установки Django выглядит одинаково во всех системах. Использовать флаг --user не нужно, как и более длинные команды вида python -m pip install имя_пакета. Помните, что с Django можно работать только в то время, пока среда (в нашем случае ll_env) остается активной.
ПРИМЕЧАНИЕ
Новая версия Django выходит приблизительно раз в восемь месяцев; возможно, при установке Django будет выведен новый номер версии. Скорее всего, проект будет работать в том виде, в котором он приведен здесь, даже в новых версиях Django. Если вы хотите использовать ту же версию Django, которая используется здесь, то введите команду pip install django==4.1.*. Будет установлен последний выпуск Django 4.1. Если у вас возникнут проблемы, связанные с версией, то обращайтесь к онлайн-ресурсам книги по адресу https://ehmatthes.github.io/pcc_3e.
Не выходя из активной виртуальной среды (пока ll_env выводится в круглых скобках), введите следующие команды для создания нового проекта:
❶ (ll_env)learning_log$ django-admin startproject ll_project .
❷ (ll_env)learning_log$ ls
ll_env ll_project manage.py
❸ (ll_env)learning_log$ ls ll_project
__init__.py asgi.py settings.py urls.py wsgi.py
Команда startproject ❶ дает Django указание создать новый проект ll_project. Благодаря точке в конце команды создается новый проект со структурой каталогов, которая упрощает развертывание приложения на сервере после завершения разработки.
ПРИМЕЧАНИЕ
Не забывайте о точке, иначе у вас могут возникнуть проблемы с конфигурацией при развертывании приложения. А если все же забыли, то удалите созданные файлы и папки (кроме ll_env) и снова выполните команду.
Команда ls (dir в Windows) ❷ показывает, что Django создает новый каталог ll_project. Вдобавок создается файл manage.py — короткая программа, которая получает команды и передает их соответствующей части Django для выполнения. Мы используем эти команды для управления такими задачами, как работа с базами данных и запуск серверов.
В каталоге ll_project находятся четыре файла ❸, важнейшими из которых являются settings.py, urls.py и wsgi.py. Файл settings.py определяет, как Django взаимодействует с вашей системой и управляет вашим проектом. Мы изменим некоторые из существующих настроек и добавим несколько новых в ходе разработки проекта. Файл urls.py сообщает Django, какие страницы следует создавать в ответ на запросы браузера. Файл wsgi.py помогает Django предоставлять созданные файлы (имя файла является сокращением от web server gateway interface (интерфейс шлюза веб-сервера)).
Django хранит бо́льшую часть информации, относящейся к проекту, в базе данных, поэтому на следующем этапе необходимо создать базу данных, с которой Django сможет работать. Введите следующую команду (все еще не покидая активную среду):
(ll_env)learning_log$ python manage.py migrate
❶ Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
--пропуск--
Applying sessions.0001_initial... OK
❷ (ll_env)learning_log$ ls
db.sqlite3 ll_env ll_project manage.py
Каждое изменение базы данных называется миграцией (migrating). Благодаря первому выполнению команды migrate Django получает указание проверить, что база данных соответствует текущему состоянию проекта. Когда мы впервые выполняем эту команду в новом проекте с помощью SQLite (подробнее о SQLite поговорим позже), Django создает новую базу данных за нас. Django сообщает о создании и подготовке базы к хранению информации, необходимой для выполнения административных операций и аутентификации ❶.
Выполнение команды ls показывает, что Django создает другой файл db.sqlite3 ❷. SQLite — база данных, работающая с одним файлом; она идеально подходит для написания простых приложений, поскольку вам не нужно пристально следить за управлением базой данных.
ПРИМЕЧАНИЕ
В активной виртуальной среде для выполнения команд manage.py предназначена команда python, даже если для запуска других программ вы используете другую команду (например, python3). В виртуальной среде команда python относится к версии Python, создавшей виртуальную среду.
Убедимся в том, что проект был создан правильно. Введите команду runserver для просмотра текущего состояния проекта:
(ll_env)learning_log$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
❶ System check identified no issues (0 silenced).
May 19, 2022 - 21:52:35
❷ Django version 4.1, using settings 'll_project.settings'
❸ Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Django запускает сервер, называемый сервером разработки (development server), чтобы вы могли просмотреть проект в своей системе и проверить, как он работает. Когда вы запрашиваете страницу, вводя URL в браузере, сервер Django отвечает на запрос; для этого он создает соответствующую страницу и отправляет страницу браузеру.
Сначала Django проверяет правильность созданного проекта ❶: выводятся версия Django и имя используемого файла настроек ❷, а затем возвращается URL, по которому доступен проект ❸. URL http://127.0.0.1:8000/ означает, что проект прослушивает запросы на порте 8000 локального хоста, то есть вашего компьютера. Термином «локальный хост» (localhost) обозначается сервер, который обрабатывает только запросы вашей системы; он не позволяет никому другому просмотреть разрабатываемые страницы.
Теперь откройте браузер и введите URL http://localhost:8000/ или http://127.0.0.1:8000/, если первый адрес не работает. Вы увидите нечто похожее на рис. 18.1 — страницу, которую создает Django, чтобы сообщить вам, что все пока работает правильно. Пока не завершайте работу сервера (но когда захотите прервать ее, это можно сделать, нажав сочетание клавиш Ctrl+C в терминале, в котором была введена команда runserver).
Рис. 18.1. Пока все работает правильно
ПРИМЕЧАНИЕ
Если вы получаете сообщение об ошибке That port is already in use («Порт уже используется»), то настройте в Django другой порт; для этого введите команду python manage.py runserver 8001 и продолжайте перебирать номера портов по возрастанию, пока не найдете открытый.
Упражнения
18.1. Новые проекты. Чтобы лучше понять, что делает Django, создайте пару пустых проектов и посмотрите, что получится. Создайте новую папку с простым именем типа tik_gram или insta_tok (за пределами каталога learning_log), перейдите в нее в терминальном окне и создайте виртуальную среду. Установите Django и выполните команду django-admin.py startproject tg_project . (убедитесь, что поставили точку в конце команды).
Просмотрите файлы и каталоги, созданные командой, и сравните их с файлами и каталогами «Журнала обучения». Проделайте это несколько раз, пока не начнете хорошо понимать, что именно создает Django при создании нового проекта, а затем по желанию удалите каталоги проектов.
Проект Django представляет собой группу отдельных приложений, совместная работа которых обеспечивает работу проекта в целом. Пока мы создадим одно приложение, которое будет выполнять бо́льшую часть работы в нашем проекте. Другое приложение для управления учетными записями пользователей будет добавлено в главе 19.
Оставьте сервер разработки выполняться в терминальном окне, открытом ранее. Откройте новое терминальное окно (или вкладку) и перейдите в каталог, содержащий файл manage.py. Активируйте виртуальную среду и выполните команду startapp:
learning_log$ source ll_env/bin/activate
(ll_env)learning_log$ python manage.py startapp learning_logs
❶ (ll_env)learning_log$ ls
db.sqlite3 learning_logs ll_env ll_project manage.py
❷ (ll_env)learning_log$ ls learning_logs/
__init__.py admin.py apps.py migrations models.py tests.py views.py
Команда startapp имя_приложения дает Django указание создать инфраструктуру, необходимую для создания приложения. Заглянув сейчас в каталог проекта, вы найдете в нем новую папку learning_logs ❶. Воспользуйтесь командой ls, чтобы увидеть, какие файлы были созданы Django ❷. Самые важные файлы в этой папке — models.py, admin.py и views.py. Файл models.py будет использоваться для определения данных, которыми нужно управлять в нашем приложении. К файлам admin.py и views.py мы вернемся позднее.
Давайте подумаем, какие данные нам понадобятся. Каждый пользователь создает в своем журнале набор тем. Любая запись, которую он сделает, будет привязана к определенной теме, а записи будут выводиться в текстовом виде. Кроме того, необходимо хранить временную метку каждой записи, чтобы пользователь знал, когда была создана эта запись.
Откройте файл models.py и просмотрите его текущее содержимое:
models.py
from django.db import models
# Создайте здесь свои модели.
Модуль models импортируется автоматически, и нам предлагается создать свои модели. Модель сообщает Django, как работать с данными, которые будут храниться в приложении. С точки зрения кода модель представляет собой обычный класс; она содержит атрибуты и методы, как и все остальные классы, с которыми вы познакомились ранее. Вот как выглядит модель тем обсуждения, которые будут сохраняться пользователями:
from django.db import models
class Topic(models.Model):
"""Тема, которую изучает пользователь."""
❶ text = models.CharField(max_length=200)
❷ date_added = models.DateTimeField(auto_now_add=True)
❸ def __str__(self):
"""Возвращает строковое представление модели."""
return self.text
Мы создали класс Topic, наследуемый от Model — родительского класса, включенного в Django и определяющего базовую функциональность модели. В класс Topic добавляются два атрибута: text и date_added.
Атрибут text содержит данные CharField — блок данных, состоящий из символов, то есть текст ❶. Атрибуты CharField могут использоваться для хранения небольших объемов текста: имен, заголовков, названий городов и т.д. При определении атрибута CharField необходимо сообщить Django, сколько места нужно зарезервировать для него в базе данных. В данном случае задается максимальная длина max_length, равная 200 символам; этого должно быть достаточно для хранения большинства имен тем.
Атрибут date_added содержит данные DateTimeField — блок данных для хранения даты и времени ❷. Благодаря аргументу auto_add_now=True Django получает указание автоматически присвоить этому атрибуту текущую дату и время каждый раз, когда пользователь создает новую тему.
Рекомендуется сообщить Django, как представлять экземпляр модели. Django вызывает метод __str__() для вывода простого представления экземпляра модели. Мы написали реализацию __str__(), которая возвращает строку, хранящуюся в атрибуте text ❸.
Полный список всех полей, которые могут использоваться в модели, приведен в документе Django Model Field Reference на сайте https://docs.djangoproject.com/en/4.1/ref/models/fields. Возможно, вся эта информация вам сейчас не понадобится, но она будет в высшей степени полезной, когда вы начнете разрабатывать собственные приложения.
Чтобы использовать модели, необходимо дать Django указание добавить приложение в общий проект. Откройте файл settings.py (из каталога ll_project) и найдите в нем раздел, который сообщает Django, какие приложения установлены в проекте.
settings.py
--пропуск--
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
--пропуск--
Добавьте наше приложение в этот список, изменив содержимое INSTALLED_APPS, чтобы оно выглядело так:
--пропуск--
INSTALLED_APPS = [
# Мои приложения
'learning_logs',
# Приложения Django по умолчанию.
'django.contrib.admin',
--пропуск--
]
--пропуск--
Группирование приложений в проекте упрощает управление ими по мере того, как проект развивается, а количество приложений увеличивается. Здесь мы создаем раздел My apps, который пока содержит только приложение 'learning_logs'. Очень важно разместить свои приложения перед приложениями по умолчанию на случай, если вам понадобится переопределить поведение последних.
Затем необходимо дать Django указание изменить базу данных для хранения информации, относящейся к модели Topic. В терминальном окне введите следующую команду:
(ll_env)learning_log$ python manage.py makemigrations learning_logs
Migrations for 'learning_logs':
learning_logs/migrations/0001_initial.py
- Create model Topic
(ll_env)learning_log$
По команде makemigrations Django определяет, как изменить базу данных для хранения информации, связанной с новыми моделями. Из результатов видно, что Django создает файл миграции 0001_initial.py. Эта миграция создает в базе данных таблицу для модели Topic.
Теперь применим миграцию для автоматического изменения базы данных:
(ll_env)learning_log$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
Applying learning_logs.0001_initial... OK
Бо́льшая часть вывода этой команды совпадает с выводом, полученным при первом выполнении команды migrate. Нам нужно проверить последнюю строку в этом выводе: здесь Django подтверждает, что применение миграции для learning_logs прошло успешно.
Каждый раз, когда вы захотите изменить данные, которыми управляет приложение «Журнал обучения», выполните эти три действия: внесите изменения в models.py, вызовите makemigrations для learning_logs и дайте Django указание выполнить миграцию проекта (migrate).
Django позволяет легко работать с моделями, определенными для приложения, через административный сайт (admin site). Он используется администраторами сайта, а не рядовыми пользователями. В этом разделе мы создадим такой сайт и используем его для добавления некоторых тем через модель Topic.
Django позволяет создать пользователя, обладающего полным набором привилегий на сайте; такой пользователь называется суперпользователем (superuser). Привилегии (privileges) управляют действиями, которые разрешено выполнять пользователю. При самом жестком уровне привилегий пользователь может только читать общедоступную информацию на сайте. Зарегистрированным пользователям обычно предоставляется привилегия чтения своих приватных данных, а также избранной информации, доступной только для участников сообщества. Эффективное администрирование веб-приложения обычно требует, чтобы владельцу сайта была доступна вся хранящаяся на нем информация. Хороший администратор внимательно относится к конфиденциальной информации пользователя, поскольку пользователи доверяют приложениям, с которыми работают.
Чтобы создать суперпользователя в Django, введите следующую команду и ответьте на запросы:
(ll_env)learning_log$ python manage.py createsuperuser
❶ Username (leave blank to use 'eric'): ll_admin
❷ Email address:
❸ Password:
Password (again):
Superuser created successfully.
(ll_env)learning_log$
При получении команды createsuperuser Django предлагает указать имя пользователя, который будет суперпользователем ❶. Здесь я ввел имя ll_admin, но вы можете добавить любое имя на свое усмотрение. Кроме того, можно ввести адрес электронной почты или оставить это поле пустым ❷. После этого следует дважды ввести пароль ❸.
ПРИМЕЧАНИЕ
Часть конфиденциальной информации может быть скрыта от администраторов сайта. Например, Django на самом деле не сохраняет введенный пароль; вместо этого сохраняется хеш — специальная строка, созданная на основе пароля. И когда в будущем вы вводите пароль, Django снова хеширует введенные данные и сравнивает результат с хранимым хешем. Если два хеша совпадают, то проверка пройдена. Если же хакер в результате атаки получит доступ к базе данных сайта, то сможет прочитать только хранящийся в базе хеш, но не пароли. При правильной настройке сайта восстановить исходные пароли из хешей почти невозможно.
Django добавляет некоторые модели (например, User и Group) на административный сайт автоматически, но модели, которые мы создали, придется регистрировать вручную.
При запуске приложения learning_logs Django создает файл admin.py в одном каталоге с models.py. Откройте файл admin.py:
admin.py
from django.contrib import admin
# Зарегистрируйте здесь ваши модели.
Чтобы зарегистрировать Topic на административном сайте, введите следующую команду:
from django.contrib import admin
from .models import Topic
admin.site.register(Topic)
Этот код импортирует регистрируемую модель Topic. Точка перед models сообщает Django, что файл models.py следует искать в одном каталоге с admin.py. Вызов admin.site.register() сообщает Django, что управление моделью должно осуществляться через административный сайт.
Теперь используйте учетную запись суперпользователя для входа на административный сайт. Введите адрес http://localhost:8000/admin/, введите имя пользователя и пароль для только что созданного суперпользователя — и увидите экран наподобие изображенного на рис. 18.2. На этой странице можно добавлять новых пользователей и группы, а также вносить изменения в уже существующие настройки. Кроме того, здесь можно работать с данными, связанными с только что определенной моделью Topic.
Рис. 18.2. Административный сайт с добавленной моделью Topic
ПРИМЕЧАНИЕ
Если в браузере появляется сообщение о недоступности веб-страницы, то убедитесь в том, что сервер Django работает в терминальном окне. Если нет, то активизируйте виртуальную среду и снова введите команду python manage.py runserver. Если у вас возникнут проблемы с просмотром проекта в любой момент в процессе разработки, то закройте все открытые терминалы и снова введите команду runserver; это станет хорошим первым шагом в процессе диагностики.
Когда модель Topic была зарегистрирована на административном сайте, добавим первую тему. Щелкните на ссылке Topics (Темы), чтобы перейти к странице Topics (Темы); она практически пуста, поскольку еще нет ни одной темы, с которой можно выполнять операции. Щелкните на ссылке Add Topic (Добавить тему); открывается форма для добавления новой темы. Введите в первом поле текст Chess (Шахматы) и щелкните на ссылке Save (Сохранить). Вы возвращаетесь к административной странице Topics (Темы), на которой появляется только что созданная тема.
Создадим вторую тему, чтобы у вас было больше данных для работы. Снова щелкните на ссылке Add Topic (Добавить тему) и создайте вторую тему Rock Climbing (Скалолазание). Ссылка Save (Сохранить) снова возвращает вас к основной странице Topics (Темы), на которой отображаются обе темы: Chess (Шахматы) и Rock Climbing (Скалолазание).
Чтобы сохранить информацию о том, что вы узнали по этим двум темам, необходимо определить модель для записей, которые пользователь делает в своих журналах. Каждая запись должна быть связана с конкретной темой. Такое отношение называется отношением «многие-к-одному» (many-to-one relationship), поскольку многие записи могут быть связаны с одной темой.
Код модели Entry выглядит так (поместите его в файл models.py):
models.py
from django.db import models
class Topic(models.Model):
--пропуск--
❶ class Entry(models.Model):
"""Информация, изученная пользователем по теме."""
❷ topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
❸ text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
❹ class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Возвращает строковое представление модели."""
❺ return f"{self.text[:50]}..."
Класс Entry наследует от базового класса Model, как и рассмотренный ранее класс Topic ❶. Первый атрибут, topic, является экземпляром ForeignKey ❷. Термин «внешний ключ» (foreign key) происходит из теории баз данных; внешний ключ содержит ссылку на другую запись в базе данных. Таким образом каждая запись связывается с конкретной темой. Каждой теме при создании присваивается ключ, или идентификатор. Если потребуется установить связь между двумя записями данных, то Django использует ключ, связанный с каждым блоком информации. Вскоре мы применим такие связи для получения всех записей, имеющих отношение к заданной теме. Аргумент on_delete=models.CASCADE сообщает Django, что при удалении темы все записи, связанные с этой темой, должны быть удалены (это называется каскадным удалением (cascading delete)).
Затем идет атрибут text, который является экземпляром TextField ❸. Полю такого типа ограничение размера не требуется, поскольку размер отдельных записей не ограничивается. Атрибут date_added позволяет отображать записи в порядке их создания и добавлять в каждую запись временну́ю метку.
Класс Meta вкладывается в класс Entry ❹. Класс Meta хранит дополнительную информацию по управлению моделью; в данном случае он позволяет задать специальный атрибут, благодаря которому Django получает указание использовать форму множественного числа Entries при обращении к нескольким записям. (Без этого Django будет использовать неправильную форму Entrys.)
Метод __str__() сообщает Django, какая информация должна отображаться при обращении к отдельным записям. Запись может быть достаточно длинным блоком текста, поэтому __str__() выводит только первые 50 символов ❺. Кроме того, добавляется многоточие — признак вывода неполного текста.
Мы добавили новую модель, поэтому миграцию базы данных необходимо провести снова. Вскоре вы привыкнете к этому процессу: изменяете models.py, выполняете команду python manage.py makemigrations имя_приложения, а затем команду python manage.py migrate.
Проведите миграцию базы данных и проверьте вывод:
(ll_env)learning_log$ python manage.py makemigrations learning_logs
Migrations for 'learning_logs':
❶ learning_logs/migrations/0002_entry.py
- Create model Entry
(ll_env)learning_log$ python manage.py migrate
Operations to perform:
--пропуск--
❷ Applying learning_logs.0002_entry... OK
Команда генерирует новую миграцию 0002_entry.py, которая сообщает Django, как изменить базу данных, чтобы можно было хранить информацию, связанную с моделью Entry ❶. При выдаче команды migrate Django подтверждает, что применение миграции прошло успешно ❷.
Модель Entry тоже необходимо зарегистрировать. Файл admin.py должен выглядеть так:
admin.py
from django.contrib import admin
from .models import Topic, Entry
admin.site.register(Topic)
admin.site.register(Entry)
Вернитесь на страницу http://localhost/admin/ — и увидите раздел Entries (Записи) в категории learning_logs. Щелкните на ссылке Add (Добавить) для Entries (Записи) или щелкните на Entries (Записи) и выберите вариант Add (Добавить). На экране должны появиться раскрывающийся список, позволяющий выбрать тему, для которой создается запись, и текстовое поле для ввода записи. Выберите в раскрывающемся списке вариант Chess (Шахматы) и добавьте запись. Вот первая запись, которую я сделал:
The opening is the first part of the game, roughly the first ten moves or so. In the opening, it’s a good idea to do three things — bring out your bishops and knights, try to control the center of the board, and castle your king.
Of course, these are just guidelines. It will be important to learn when to follow these guidelines and when to disregard these suggestions.
Если вы щелкнете на ссылке Save (Сохранить), то вернетесь к основной административной странице. Здесь проявляются преимущества использования формата text[:50] в качестве строкового представления каждой записи; работать с несколькими записями в административном интерфейсе намного удобнее, если вы видите только часть записи вместо ее полного текста.
Создайте вторую запись для темы Chess (Шахматы) и одну запись для темы Rock Climbing (Скалолазание), чтобы у нас были исходные данные для дальнейшей разработки «Журнала обучения». Вот вторая запись для темы Chess (Шахматы):
In the opening phase of the game, it’s important to bring out your bishops and knights. These pieces are powerful and maneuverable enough to play a significant role in the beginning moves of a game.
А вот первая запись для темы Rock Climbing (Скалолазание):
One of the most important concepts in climbing is to keep your weight on your feet as much as possible. There’s a myth that climbers can hang all day on their arms. In reality, good climbers have practiced specific ways of keeping their weight over their feet whenever possible.
Эти три записи нам пригодятся, когда мы продолжим разработку приложения «Журнал обучения».
Введенные данные можно проанализировать на программном уровне в интерактивном терминальном сеансе. Эта интерактивная среда, называемая оболочкой (shell) Django, прекрасно подходит для тестирования и диагностики проекта. Вот пример сеанса, проходящего в интерактивной оболочке:
(ll_env)learning_log$ python manage.py shell
❶ >>> from learning_logs.models import Topic
>>> Topic.objects.all()
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]>
Команда python manage.py shell (выполняемая в активной виртуальной среде) запускает интерпретатор Python, который может использоваться для работы с информацией в базе данных проекта. В данном случае мы импортируем модель Topic из модуля learning_logs.models ❶. Затем метод Topic.objects.all() используется для получения всех экземпляров модели Topic; возвращаемый список называется итоговым набором (queryset).
Содержимое итогового набора перебирается точно так же, как и содержимое списка. Например, просмотр идентификаторов, назначенных каждому объекту темы, выполняется так:
>>> topics = Topic.objects.all()
>>> for topic in topics:
... print(topic.id, topic)
...
1 Chess
2 Rock Climbing
Итоговый набор сохраняется в topics, после чего выводятся атрибут id каждого объекта topic и его строковое представление. Мы видим, что теме Chess присвоен идентификатор 1, а Rock Climbing — идентификатор 2.
Зная идентификатор конкретного объекта, можно получить его с помощью метода Topic.objects.get() и проанализировать содержащиеся в нем атрибуты. Просмотрим значения text и date_added для темы Chess:
>>> t = Topic.objects.get(id=1)
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2022, 5, 20, 3, 33, 36, 928759,
tzinfo=datetime.timezone.utc)
Кроме того, можно просмотреть записи, относящиеся к конкретной теме. Ранее мы определили атрибут topic для модели Entry. Он был экземпляром ForeighKey, представляющим связь между записью и темой. Django может использовать эту связь для получения всех записей, относящихся к некой теме:
❶ >>> t.entry_set.all()
<QuerySet [<Entry: The opening is the first part of the game, roughly...>,
<Entry:
In the opening phase of the game, it's important t...>]>
Чтобы получить данные через отношение по внешнему ключу, используйте имя связанной модели, записанное в нижнем регистре, за которым следует символ подчеркивания и слово set ❶. Допустим, у вас есть модели Pizza и Topping, и вторая связана с первой через внешний ключ. Если ваш объект называется my_pizza, то для получения всех связанных с ним экземпляров Topping используется выражение my_pizza.topping_set.all().
Мы будем задействовать такой синтаксис при переходе к программированию страниц, которые могут запрашивать пользователи. Оболочка очень удобна, когда вы хотите проверить, получает ли ваш код нужные данные. Если в оболочке код работает как задумано, то можно ожидать, что он будет правильно работать и в файлах, которые вы создаете в своем проекте. Если код выдает ошибки или не загружает данные, которые должен загружать, то вам будет намного проще отладить его в простой оболочке, чем при работе с файлами, генерирующими веб-страницы. В книге мы не будем часто возвращаться к оболочке, но вам не стоит забывать о ней — это полезный инструмент, который поможет вам освоить синтаксис Django, позволяющий работать с данными проекта.
При каждом изменении модели необходимо перезапустить оболочку, чтобы увидеть результаты этих изменений. Чтобы завершить сеанс работы с оболочкой, нажмите сочетание клавиш Сtrl+D; в Windows нажмите сочетание клавиш Ctrl+Z, а затем клавишу Enter.
Упражнения
18.2. Короткие записи. Метод __str__() в модели Entry в настоящее время присоединяет многоточие к каждому экземпляру Entry, отображаемому Django на административном сайте или в оболочке. Добавьте в метод __str__() оператор if, добавляющий многоточие только для записей, длина которых превышает 50 символов. Воспользуйтесь административным сайтом, чтобы ввести запись длиной менее 50 символов, и убедитесь, что при ее просмотре многоточие не отображается.
18.3. Django API. При написании кода для работы с данными проекта вы создаете запрос. Просмотрите документацию по созданию запросов к данным, доступную по адресу https://docs.djangoproject.com/en/4.1/topics/db/queries. Многое из того, что вы увидите, покажется новым, но эта информация пригодится, когда вы начнете работать над собственными проектами.
18.4. Пиццерия. Создайте новый проект pizzeria_project, содержащий приложение pizzas. Определите модель Pizza с полем name, в котором хранятся названия видов пиццы (например, «Гавайская» (Hawaiian) или «Любители мяса» (Meat Lovers)). Определите модель Topping с полями pizza и name. Поле pizza должно содержать внешний ключ к модели Pizza, а поле name должно позволять хранить такие значения, как pineapple, Canadian bacon и sausage.
Зарегистрируйте обе модели на административном сайте. Используйте сайт для ввода названий пиццы и начинок. Изучите введенные данные в интерактивной оболочке.
Обычно процесс создания веб-страниц в Django состоит из трех стадий: определения URL, написания представлений и шаблонов. Сначала следует определить схему (pattern) URL. Она описывает структуру адреса и сообщает Django, на какие компоненты следует обращать внимание при сопоставлении запроса браузера с URL на сайте, чтобы выбрать возвращаемую страницу.
Затем каждый URL связывается с конкретным представлением (view) — функция представления читает и обрабатывает данные, необходимые странице. Функция представления часто вызывает шаблон (template), который создает страницу, подходящую для передачи браузеру. Чтобы вы лучше поняли, как работает этот механизм, создадим главную страницу приложения «Журнал обучения». Мы определим URL главной страницы, напишем для него функцию представления и создадим простой шаблон.
Сейчас мы всего лишь убеждаемся в том, что «Журнал обучения» работает как положено, поэтому страница пока останется простой. Когда приложение будет завершено, вы можете заниматься его оформлением сколько захотите; приложение, которое хорошо выглядит, но не работает, бессмысленно. Пока что на главной странице будут отображаться только заголовок и краткое описание.
Пользователь запрашивает страницы, вводя URL в браузере и щелкая на ссылках, поэтому мы должны решить, какие URL понадобятся в нашем проекте. Начнем с URL главной страницы: это базовый адрес, используемый для обращения к проекту. На данный момент базовый URL http://localhost:8000/ возвращает сайт, сгенерированный Django по умолчанию; он сообщает о том, что проект был создан успешно. Мы изменим главную страницу, связав базовый URL с главной страницей «Журнала обучения».
В главной папке проекта ll_project откройте файл urls.py. Вы увидите в нем следующий код:
ll_project/urls.py
❶ from django.contrib import admin
from django.urls import path
❷ urlpatterns = [
❸ path('admin/', admin.site.urls),
]
Первые две строки импортируют модуль admin и функцию для создания путей URL ❶. В теле файла определяется переменная urlpatterns ❷. В файле urls.py, представляющем проект в целом, переменная urlpatterns добавляет наборы URL из приложений в проект. Список содержит модуль admin.site.urls, определяющий все URL, которые могут запрашиваться с административного сайта ❸.
Добавим в этот файл URL для learning_logs:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('learning_logs.urls')),
]
Мы импортировали функцию include(), а также добавили строку кода для добавления модуля learning_logs.urls.
Файл urls.py по умолчанию находится в папке ll_project; теперь нужно создать второй файл urls.py в папке learning_logs. Создайте новый файл Python, сохраните его под именем urls.py в learning_logs, и добавьте в него следующий код:
learning_logs/urls.py
❶ """Определяет схемы URL для learning_logs."""
❷ from django.urls import path
❸ from . import views
❹ app_name = 'learning_logs'
❺ urlpatterns = [
# Главная страница
❻ path('', views.index, name='index'),
]
Чтобы было понятно, с какой версией urls.py мы работаем, в начало файла добавляется строка документации ❶. Затем импортируется функция path, она необходима для связывания URL с представлениями ❷. Кроме того, импортируется модуль views ❸; благодаря точке Python получает указание импортировать модуль views.py из каталога, в котором находится текущий модуль urls.py. Переменная app_name помогает Django отличить этот файл urls.py от одноименных файлов в других приложениях проекта ❹. Переменная urlpatterns в этом модуле представляет собой список страниц, которые могут запрашиваться из приложения learning_logs ❺.
Схема URL представляет собой вызов функции path() с тремя аргументами ❻. Первый содержит строку, которая помогает Django правильно маршрутизировать текущий запрос. Django получает запрашиваемый URL и пытается отобразить его на представление. Для этого он ищет среди всех определенных схем URL ту, которая соответствует текущему запросу. Базовый URL проекта (http://localhost:8000/) игнорируется, так что пустая строка совпадает с базовым URL. Любой другой URL не будет соответствовать этому выражению, и Django вернет страницу с ошибкой, если запрашиваемый URL не соответствует ни одной из существующих схем.
Второй аргумент в path() ❻ определяет вызываемую функцию из views.py. Когда запрашиваемый URL соответствует регулярному выражению, Django вызывает index() из views.py (мы напишем эту функцию представления в следующем подразделе). Третий аргумент определяет имя index для этой схемы URL, чтобы на нее можно было ссылаться в других частях кода. Каждый раз, когда потребуется предоставить ссылку на главную страницу, мы будем использовать это имя вместо URL.
Функция представления получает информацию из запроса, подготавливает данные, необходимые для создания страницы, и возвращает данные браузеру — часто с помощью шаблона, который определяет внешний вид страницы.
Файл views.py в папке learning_logs был сгенерирован автоматически при выполнении команды python manage.py startapp. На данный момент его содержимое выглядит так:
views.py
from django.shortcuts import render
# Создайте здесь свои представления.
Сейчас файл только импортирует функцию render(), которая генерирует ответ на основании данных, полученных от представлений. Откройте файл представления и добавьте следующий код главной страницы:
from django.shortcuts import render
def index(request):
"""Главная страница приложения "Журнал обучения"."""
return render(request, 'learning_logs/index.html')
Если URL запроса совпадает с только что определенной схемой, Django ищет в файле views.py функцию представления index(), после чего передает ей объект request. В нашем случае никакая обработка данных для страницы не нужна, поэтому код функции сводится к вызову функции render(). Она использует два аргумента: исходный объект запроса и шаблон, применяемый для создания страницы. Напишем этот шаблон.
Шаблон определяет общий внешний вид страницы, а Django заполняет его соответствующими данными при каждом запросе страницы. Шаблон может обращаться к любым данным, полученным от представления. Наше представление главной страницы никаких данных не предоставляет, поэтому шаблон получается относительно простым.
В папке learning_logs создайте новую папку templates, а в ней — папку learning_logs. На первый взгляд такая структура кажется избыточной (папка learning_logs в папке templates в папке learning_logs), но созданную таким образом структуру Django будет интерпретировать однозначно даже в контексте большого проекта, состоящего из множества отдельных приложений. Во внутренней папке learning_logs создайте новый файл index.html (таким образом, полное имя файла имеет вид ll_project/learning_logs/templates/learning_logs/index.html). Добавьте в него следующий текст:
index.html
<p>Learning Log</p>
<p>Learning Log helps you keep track of your learning, for any topic you're interested in.</p>
Это очень простой файл. Если вы не знакомы с синтаксисом HTML, то теги <p></p> обозначают абзацы: <p> открывает абзац, а </p> закрывает его. Наша страница содержит два абзаца: в первом находится заголовок, а во втором — описание, что пользователь может сделать с помощью приложения «Журнал обучения».
Теперь при запросе базового URL проекта http://localhost:8000/ вы увидите только что созданную страницу вместо страницы по умолчанию. Django берет запрошенный URL и видит, что он совпадает со схемой ''. В этом случае Django вызывает функцию views.index(), что приводит к созданию страницы с помощью шаблона, содержащегося в index.html. Полученная страница показана на рис. 18.3.
Рис. 18.3. Главная страница «Журнала обучения»
И хотя может показаться, что для одной страницы этот процесс слишком сложен, такое разделение URL, представлений и шаблонов очень удобно. Оно позволяет сосредоточиться на отдельных аспектах проекта, а в более крупных проектах некоторые участники могут сосредоточиться на областях, в которых они наиболее сильны. Например, специалист по базам данных может заняться моделями, программист — кодом представления, а веб-дизайнер — шаблонами.
ПРИМЕЧАНИЕ
Вы можете получить следующее сообщение об ошибке:
ModuleNotFoundError: No module named 'learning_logs.urls'
В таком случае остановите сервер разработки, нажав сочетание клавиш Ctrl+C в терминальном окне, в котором была введена команда runserver. Затем снова введите команду python manage.py runserver. Каждый раз, когда вы сталкиваетесь с подобными ошибками, попробуйте остановить и перезапустить сервер.
Упражнения
18.5. План питания. Представьте приложение для составления плана питания на неделю. Создайте новую папку meal_planner, затем создайте в ней новый проект Django. Создайте новое приложение meal_plans. Сформируйте простую главную страницу для этого проекта.
18.6. Главная страница пиццерии. Добавьте главную страницу в проект «Пиццерия», который вы начали создавать в упражнении 18.4.
Теперь, когда вы начали представлять процесс создания страниц, можно переходить к проекту «Журнал обучения». Мы создадим две страницы для вывода данных: на одной будет отображаться список всех тем, а на другой — все записи по конкретной теме. Для каждой страницы мы добавим схему URL, напишем функцию представления и создадим шаблон. Но прежде чем переходить к работе, нужно создать базовый шаблон, от которого смогут наследовать все шаблоны этого проекта.
При создании сайта некоторые элементы почти всегда повторяются на каждой странице. Вместо того чтобы встраивать эти элементы непосредственно в страницы, вы можете написать базовый шаблон с повторяющимися элементами; все страницы будут наследовать от него. Такое решение позволит сосредоточиться на разработке уникальных аспектов каждой страницы и существенно упростит изменение общего оформления проекта в целом.
Начнем с создания шаблона base.html в одном каталоге с файлом index.html. Этот файл будет содержать элементы, общие для всех страниц; все остальные шаблоны наследуют от base.html. Пока единственным элементом, который должен повторяться на каждой странице, остается заголовок в верхней части страницы. Шаблон будет добавляться в каждую страницу, поэтому преобразуем заголовок в ссылку на главную страницу:
base.html
<p>
❶ <a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>
❷ {% block content %}{% endblock content %}
Первая часть файла создает абзац с именем проекта, который также работает как ссылка на главную страницу. Для создания ссылки использовался шаблонный тег (template tag), обозначенный фигурными скобками и знаками процента {% %}. Шаблонный тег представляет собой блок кода, который генерирует информацию для вывода на странице. В данном примере шаблонный тег {% url 'learning_logs:index' %} генерирует URL, соответствующий схеме URL с именем 'index', определенной в файле learning_logs/urls.py ❶. В данном примере learning_logs — пространство имен, а index — схема URL, имеющая уникальное имя в этом пространстве. Данное пространство определяется значением, присвоенным переменной app_name в файле learning_logs/urls.py.
В этой простой странице HTML ссылка заключается в якорный тег (anchor tag) <a>:
<a href="url_ссылки">текст ссылки</a>
Генерирование URL с помощью шаблонного тега сильно упрощает актуализацию ссылок. Чтобы изменить URL в проекте, достаточно изменить схему URL в urls.py, а Django автоматически вставит обновленный URL при следующем запросе страницы. Каждая страница в проекте будет наследовать от base.html, так что в дальнейшем на каждой странице будет содержаться ссылка на главную страницу.
В последней строке вставляется пара тегов block ❷. Блок content резервирует место; попадающая в него информация будет определяться дочерним шаблоном.
Дочерний шаблон не обязан определять каждый блок в своем родителе, так что в родительских шаблонах можно зарезервировать место для любого количества блоков, а дочерний шаблон будет использовать столько из них, сколько потребуется.
ПРИМЕЧАНИЕ
В коде Python почти всегда используются отступы в четыре пробела. Файлы шаблонов обычно имеют больший уровень вложенности, чем файлы Python, поэтому каждый уровень отступа обычно обозначается двумя пробелами. Будьте внимательны и действуйте последовательно.
Теперь нужно переписать файл index.html так, чтобы он наследовал от base.html. Обновленный файл index.html выглядит так:
index.html
❶ {% extends "learning_logs/base.html" %}
❷ {% block content %}
<p>Learning Log helps you keep track of your learning, for any topic
you're interested in.</p>
❸ {% endblock content %}
Сравнивая этот файл с исходной версией index.html, мы видим, что заголовок Learning Log заменен кодом наследования от родительского шаблона ❶. В первой строке дочернего шаблона должен находиться тег {% extends %}, который сообщает Django, от какого родительского шаблона он наследует. Файл base.html является частью learning_logs, поэтому имя папки learning_logs добавляется в путь к родительскому шаблону. Эта строка извлекает все содержимое из шаблона base.html и позволяет index.html определить, что должно попасть в пространство, зарезервированное блоком content.
Блок content определяется вставкой тега {% block %} с именем content ❷. Все, что не наследуется от родительского шаблона, попадает в блок content. В данном случае это абзац с описанием проекта «Журнал обучения». О том, что определение content завершено, мы сообщаем с помощью тега {% endblock content %} ❸. Наличие имени у тега {% endblock %} не обязательно, но если шаблон увеличится и будет содержать несколько блоков, то будет полезно сразу видеть, какой именно блок завершается.
Вероятно, вы уже начинаете понимать преимущества наследования шаблонов: в дочерний шаблон достаточно добавить информацию, уникальную для этой страницы. Такой подход упрощает не только каждый шаблон, но и сам процесс изменения сайта. Чтобы изменить элемент, общий для многих страниц, достаточно поправить элемент в родительском шаблоне. Внесенные изменения будут автоматически перенесены на каждую страницу, наследующую от этого шаблона. В проекте из десятков и сотен страниц такая структура позволяет значительно упростить и ускорить доработку сайта.
В больших проектах часто создаются один родительский шаблон base.html для всего сайта и родительские шаблоны для каждого его крупного раздела. Все шаблоны разделов наследуют от base.html, и каждая страница сайта наследует от шаблона раздела. При такой структуре вы сможете легко изменять оформление и поведение самого сайта, любого его раздела или отдельной страницы. Данная конфигурация позволяет сильно повысить эффективность работы и стимулирует разработчика к дальнейшему совершенствованию своего проекта.
Разобравшись с тем, как эффективно организовать создание страниц, мы можем сосредоточиться на следующих двух страницах: списке всех тем и списке записей по одной теме. На странице тем выводится перечень всех тем, созданных пользователями, и это первая страница, на которой нам придется работать с данными.
Сначала нужно определить URL для страницы тем. Обычно в таких случаях выбирается простой фрагмент URL, который отражает суть информации, представленной на странице. Мы возьмем слово topics, так что для получения страницы будет использоваться URL http://localhost:8000/topics/. А вот какие изменения следует внести в файл learning_logs/urls.py:
learning_logs/urls.py
"""Определяет схемы URL для learning_logs."""
--пропуск--
urlpatterns = [
# Главная страница.
path('', views.index, name='index'),
# Страница со списком всех тем.
❶ path('topics/', views.topics, name='topics'),
]
Мы просто добавили topics/ в аргумент регулярного выражения, используемый с URL главной страницы ❶. Когда Django проверяет запрашиваемый URL, эта схема совпадет с любым URL, который состоит из базового URL и слова topics. Косую черту в конце можно либо добавить, либо нет, но после слова topics ничего быть не должно, иначе схема не совпадет. Любой запрос с URL, соответствующим этой схеме, будет передан функции topics() в файле views.py.
Функция topics() должна получать данные из базы данных и отправлять их шаблону. Обновленная версия файла views.py выглядит так:
views.py
from django.shortcuts import render
❶ from .models import Topic
def index(request):
--пропуск--
❷ def topics(request):
"""Выводит список тем."""
❸ topics = Topic.objects.order_by('date_added')
❹ context = {'topics': topics}
❺ return render(request, 'learning_logs/topics.html', context)
Сначала импортируется модель, связанная с нужными данными ❶. Функции topics() необходим один параметр: объект request, полученный Django от сервера ❷. База данных получает запрос на получение объектов Topic, отсортированных по атрибуту date_added ❸. Полученный итоговый набор сохраняется в topics.
Затем определяется контекст, который будет передаваться шаблону ❹. Контекст (context) представляет собой словарь, в котором ключами являются имена, используемые в шаблоне для обращения к данным, а значениями — данные, которые должны передаваться шаблону. В данном случае существует всего одна пара «ключ — значение», которая содержит набор тем, отображаемых на странице. При создании страницы, использующей данные, функции render() передается словарь context, а также объект request и путь к шаблону ❺.
Шаблон страницы со списком тем получает словарь context, чтобы использовать данные, предоставленные функцией topics(). Создайте файл topics.html в одном каталоге с index.html. Вывод списка тем в шаблоне осуществляется следующим образом:
topics.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics</p>
❶ <ul>
❷ {% for topic in topics %}
❸ <li>{{ topic }}</li>
❹ {% empty %}
<li>No topics have been added yet.</li>
❺ {% endfor %}
❻ </ul>
{% endblock content %}
Сначала тег {% extends %} объявляет о наследовании от base.html, как и в случае с шаблоном index, после чего открывается блок content. Тело страницы содержит маркированный (bulleted) список введенных тем. В стандартном языке HTML маркированный список называется неупорядоченным (unordered) и обозначается тегами <ul></ul>. Список тем начинается с открывающегося тега <ul> ❶.
Далее шаблонный тег, эквивалентный циклу for, применяется для перебора списка topics из словаря context ❷. Код, используемый в шаблоне, отличается от Python несколькими важными моментами. В Python для обозначения строк, входящих в тело цикла, используются отступы. В шаблоне каждый цикл for должен иметь явный тег {% endfor %}, обозначающий конец цикла. Таким образом, в шаблонах часто встречаются циклы следующего вида:
{% for элемент in список %}
действия для каждого элемента
{% endfor %}
В цикле каждая тема должна быть преобразована в элемент маркированного списка. Чтобы вывести значение переменной в шаблоне, заключите ее имя в двойные фигурные скобки. Они на странице не появятся и всего лишь сообщают Django об использовании шаблонной переменной. Код {{ topic }} ❸ будет заменен значением topic при каждом проходе цикла. Тег HTML <li></li> обозначает элемент списка (list item). Все, что находится между тегами, в паре тегов <ul></ul>, будет отображаться как элемент маркированного списка.
Шаблонный тег {% empty %} ❹ сообщает Django, что делать при отсутствии элементов в списке. В нашем примере выводится сообщение о том, что темы еще не созданы. Последние две строки завершают цикл for ❺ и маркированный список ❻.
Теперь необходимо изменить базовый шаблон и вставить ссылку на страницу с темами. Добавьте следующий код в base.html:
base.html
<p>
❶ <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
❷ <a href="{% url 'learning_logs:topics' %}">Topics</a>
</p>
{% block content %}{% endblock content %}
Сначала добавляется ссылка на главную страницу ❶, а затем дефис, после которого вставляется ссылка на страницу тем, которая также представлена шаблонным тегом {% url %} ❷. Эта строка дает Django указание сгенерировать ссылку, соответствующую схеме URL с именем 'topics' в файле learning_logs/urls.py.
Обновив главную страницу в браузере, вы увидите ссылку Topics (Темы). Щелчок на ней открывает страницу, похожую на рис. 18.4.
Рис. 18.4. Страница со списком тем
Далее создадим страницу для вывода информации по одной теме, с названием темы и всеми записями по этой теме. Мы снова определим новую схему URL, напишем представление и создадим шаблон. Кроме того, на странице со списком тем каждый элемент маркированного списка будет преобразован в ссылку на соответствующую страницу отдельной темы.
Схема URL для страницы отдельной темы немного отличается от других схем URL, которые встречались нам ранее, поскольку в ней используется атрибут id темы для обозначения запрашиваемой темы. Например, если пользователь хочет просмотреть страницу с подробной информацией по теме Chess (id=1), эта страница будет иметь URL http://localhost:8000/topics/1/. Вот как выглядит схема для этого URL из файла learning_logs/urls.py:
learning_logs/urls.py
--пропуск--
urlpatterns = [
--пропуск--
# Страница с подробной информацией по отдельной теме.
path('topics/<int:topic_id>/', views.topic, name='topic'),
]
Рассмотрим строку 'topics/<int:topic_id>/' в этой схеме URL. Первая часть строки сообщает Django, что искать следует URL, у которых за базовым адресом идет слово topics. Вторая часть строки, /<int:topic_id>/, описывает целое число, помещенное между двумя косыми чертами; оно сохраняется в аргументе topic_id.
Когда Django находит URL, соответствующий этой схеме, вызывается функция представления topic(), в аргументе которой передается значение, хранящееся в topic_id. Значение topic_id используется для получения нужной темы внутри функции.
Функция topic() должна получить тему и все связанные с ней записи из базы данных, подобно тому как мы делали это ранее в оболочке Django:
views.py
--пропуск--
❶ def topic(request, topic_id):
"""Выводит одну тему и все ее записи."""
❷ topic = Topic.objects.get(id=topic_id)
❸ entries = topic.entry_set.order_by('-date_added')
❹ context = {'topic': topic, 'entries': entries}
❺ return render(request, 'learning_logs/topic.html', context)
Это первая функция представления, которой требуется параметр, отличающийся от объекта request. Функция получает значение, совпавшее с выражением /<int:topic_id>/, и сохраняет его в topic_id ❶. Затем функция get() используется для получения темы, по аналогии с тем, как мы это делали в оболочке Django ❷. Далее загружаются записи, связанные с данной темой, и они упорядочиваются по значению date_added ❸: благодаря знаку «минус» перед date_added результаты сортируются в обратном порядке, то есть самые последние записи будут находиться на первых местах. Тема и записи сохраняются в словаре context ❹, после чего вызывается функция render() с объектом запроса, шаблоном topic.html и словарем context ❺.
ПРИМЕЧАНИЕ
Выражения в строках ❷ и ❸, которые обращаются к базе данных за конкретной информацией, называются запросами. Когда вы пишете подобные запросы для своих проектов, сначала опробуйте их в оболочке Django. Вы сможете проверить результат намного быстрее, чем если напишете представление и шаблон, а затем проверите результаты в браузере.
В шаблоне должны отображаться название темы и текст записей. Кроме того, необходимо отправить пользователю сообщение, если по теме еще не было сделано ни одной записи:
topic.html
{% extends 'learning_logs/base.html' %}
{% block content %}
❶ <p>Topic: {{ topic }}</p>
<p>Entries:</p>
❷ <ul>
❸ {% for entry in entries %}
<li>
❹ <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
❺ <p>{{ entry.text|linebreaks }}</p>
</li>
❻ {% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
Шаблон расширяет base.html, как и для всех страниц проекта. Затем выводится атрибут text темы, которая была запрошена ❶. Переменная topic доступна, поскольку добавлена в словарь context. Затем создается маркированный список со всеми записями по теме ❷; перебор записей осуществляется так же, как это делалось ранее для тем ❸.
С каждым элементом списка связываются два значения: временна́я метка и полный текст каждой записи. Для временно́й метки ❹ выводится значение атрибута date_added. В шаблонах Django вертикальная черта (|) представляет фильтр — функцию, изменяющую значение шаблонной переменной. Фильтр date:'M d, Y H:i' выводит временны́е метки в формате January 1, 2022 23:00. Следующая строка выводит полное значение атрибута text текущей записи. Фильтр linebreaks ❺ следит за тем, чтобы длинный текст содержал разрывы строк в формате, поддерживаемом браузером (вместо блока непрерывного текста). Шаблонный тег {% empty %} ❻ используется для вывода сообщения об отсутствии записей.
Прежде чем просматривать страницу отдельной темы в браузере, необходимо изменить шаблон списка так, чтобы каждая тема вела на соответствующую страницу. Внесите в файл topics.html следующие изменения:
topics.html
--пропуск--
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">
{{ topic }}</a>
</li>
{% empty %}
--пропуск--
Шаблонный тег URL используется для генерирования ссылки на основании схемы URL с именем 'topic' из learning_logs. Этой схеме необходим аргумент topic_id, поэтому в шаблонный тег URL добавляется атрибут topic.id. Теперь каждая тема в списке представляет собой ссылку на страницу темы — например, http://localhost:8000/topics/1/.
Если теперь обновить страницу тем и щелкнуть на теме, то открывается страница, изображенная на рис. 18.5.
Рис. 18.5. Страница со списком всех записей по отдельной теме
ПРИМЕЧАНИЕ
Между topic.id и topic_id существует неочевидное, но важное различие. Выражение topic.id проверяет тему и получает значение соответствующего идентификатора. Переменная topic_id содержит ссылку на этот идентификатор в коде. Если вы столкнетесь с ошибками при работе с идентификаторами, то убедитесь, что эти выражения используются правильно.
Упражнения
18.7. Документация шаблонов. Просмотрите документацию по шаблонам Django, доступную по адресу https://docs.djangoproject.com/en/2.2/ref/templates/. Используйте ее в работе над собственными проектами.
18.8. Страницы проекта «Пиццерия». Добавьте страницу в проект Pizzeria из упражнения 18.6, на которой показываются названия видов пиццы. Свяжите каждое название со страницей, на которой выводится список начинок к этой пицце. Обязательно примените наследование шаблонов, чтобы повысить эффективность создания страниц.
В этой главе вы начали осваивать создание веб-приложений с помощью инфраструктуры Django. Вы написали короткую спецификацию проекта, установили Django в виртуальной среде, узнали, как настроить проект, и проверили правильность настройки. Вы узнали, как создать приложение и определить модели для представления данных в вашем приложении. Кроме того, вы познакомились с базами данных и узнали, как Django упрощает миграцию баз данных после внесения изменений в модель. Вы научились создавать суперпользователей для административного сайта, а также использовали такой сайт для ввода исходных данных.
Помимо этого, в данной главе была представлена оболочка Django, позволяющая работать с данными проекта в терминальном сеансе. Вы научились определять URL, создавать функции представления и писать шаблоны для создания страниц сайта. Наконец, вы применили механизм наследования шаблонов, который упрощает структуру отдельных шаблонов и облегчает изменение сайта по мере развития проекта.
В главе 19 вы создадите интуитивно понятные, удобные страницы, на которых пользователи смогут добавлять новые темы и записи, а также редактировать существующие записи без участия административного сайта. Кроме того, вы добавите систему регистрации пользователей, чтобы любой из них мог создать учетную запись и вести свой журнал. Собственно, в этом и заключается сущность веб-приложения — создание функциональности, которую может применять любое количество пользователей.