Руководство часть 5: Создание домашней страницы

Теперь мы готовы создать код нашей первой страницы — домашняя страница сайта LocalLibrary будет показывать количество записей в каждой модели, кроме того, она будет выводить боковую навигационную панель с ссылками на другие страницы сайта. В результате мы приобретём практический навык написания простых URL-преобразований и отображений, получения записей из базы данных и применения шаблонов.

Требования: Прочитать Введение в Django. Завершить изучение предыдущих частей руководства (включая Руководство часть 4: Django административный раздел сайта).
Цель: Понимать как создавать простые url-преобразования (которые не содержат никаких данных) и отображения, как получать данные из моделей и создавать шаблоны.

Обзор

Теперь, когда мы определили наши модели и создали несколько записей в них, пришло время написать код, который будет показывать данную информацию пользователям. И первое, что нам необходимо сделать это определиться какую информацию мы бы хотели показывать на наших страницах, а затем определить соответствующие URL-адреса для получения соответствующих ресурсов. Затем нам надо создать url-преобразования, отображения (функции, или классы), а затем шаблоны страницы.

Диаграмма, представленная ниже, демонстрирует главный поток данных и элементов, которые нужно реализовать для управления HTTP запросами и ответами. Поскольку мы уже создали модель, то нам остаётся создать следующее:

  • URL-преобразования для перехода по соответствующему URL-адресу (с учётом информации, передаваемой в данном адресе) к соответствующей функции отображения.
  • Функции отображения для запроса соответствующих данных из моделей, создание страниц HTML для показа этих данных и их отправку в клиент пользователя (в браузер).
  • Шаблоны, которые используются отображениями для рендеринга (отрисовки) данных.

Как вы увидите в следующем разделе, у нас будет 5 страниц, которые мы немного опишем в данной статье. Данная статья, большей частью, будет сконцентрирована на реализации всего-лишь одной, домашней страницы нашего сайта (к другим страницам мы перейдём в других частях руководства). Это должно дать вам хорошее базовое представление о работе с URL-преобразованиями (связывании), отображениями и моделями.

Определяем URL-адреса страниц

По сути, так как для конечных пользователей, данная версия сайта LocalLibrary является read-only (только для чтения), то нам надо создать домашнюю страницу и страницы, которые будут показывать списки авторов и книг, а также детальную информацию о них, соответственно.

Перечислим URL-адреса, которые понадобятся для наших страниц:

  • catalog/ — Домашняя/индексная страница.
  • catalog/books/ — Список всех книг.
  • catalog/authors/ — Список всех авторов.
  • catalog/book/<id> — Детальная информация для определённой книги со значением первичного ключа равного <id>. Например, /catalog/book/3, для id = 3.
  • catalog/author/<id> — Детальная информация для определённого автора со значением первичного ключа равного <id>. Например, /catalog/author/11, для автора с id = 11.

Первые три URL-адреса используются для показа домашней страницы, а также списков книг и авторов. Они не кодируют никакой дополнительной информации и результат показа данных страниц будет полностью зависеть от того, что находится в базе данных и, по сути, будет все время одним и тем же (при неизменной базе данных, конечно).

Последние два URL-адреса применяются для показа детальной информации об определённой книге, или авторе — в себе они содержат соответствующее значение идентификатора (показан как <id>, выше). URL-преобразование получает данную информацию и передаёт её в отображение, которое применяет её для запроса к базе данных. Для кодирования и применения данной информации в вашем URL-адресе нам понадобится только одно url-преобразование, соответствующее отображение и шаблон страницы для показа любой книги (или автора).

Примечание: Django позволяет вам конструировать ваши URL-адреса любым, удобным для вас, способом — вы можете закодировать информацию в теле URL-адреса, как показано выше, или использовать URL-адрес типа GET (например, /book/?id=6). Независимо от ваших предпочтений, URL-адреса должны быть понятными, логичными и читабельными (посмотрите совет W3C здесь).

Документация Django рекомендует кодировать информацию в теле URL-адреса, на практике это приводит к лучшей структуре сайта.

Как было отмечено ранее, оставшаяся часть данной статьи описывает как сделать главную страницу сайта.

Создание главной страницы сайта

Первой страницей, которую мы создадим, будет главная страница сайта (catalog/). Она будет небольшой статической HTML-страницей, которая будет показывать вычисленные "количества" различных записей из базы данных. Для того, чтобы проделать данную работу мы вначале создадим URL-преобразование, затем отображение и шаблон.

Примечание: Лучше уделить больше внимания на данный раздел, поскольку информация, представленная здесь, применяется для создания всех страниц сайта.

URL-преобразование

Когда мы создавали скелет сайта мы обновили locallibrary/urls.py так что всякий раз, когда начинается URL-адрес наш catalog/ получен и URLConf catalog.urls подключён для обработки оставшейся части строки.

urlpatterns += [
    path('catalog/', include('catalog.urls')),
]

Примечание: всякий раз, когда Django сталкивается c django.urls.include() он отбрасывает часть совпавшего URL , и отправляет оставшуюся строку в включённый URLconf для дальнейшей обработки.

Внутри нашего каталога приложения откройте urls.py и поместите в него текст, отмеченный жирным, ниже.

python
urlpatterns = [
    path('', views.index, name='index'),
]

Эта функция path() определяет URL-паттерн (в данном случае это пустая строка: '' - мы поговорим чуть более подробно о них далее в данном руководстве) и функцию отображения, которая будет вызвана, если введённый адрес будет соответствует данному паттерну (views.index — это функция с именем index() в views.py).

Данная функция path(), кроме того, определяет параметр name, который уникально определяет это частное URL-преобразование. Вы можете использовать данное имя для "обратного" ("reverse") преобразования — то есть, для динамического создания URL-адреса, указывающего на ресурс, на которое указывает данное преобразование. Например, теперь, когда у нас имеется данное символическое имя, мы можем ссылаться на нашу домашнюю страницу при помощи создания следующей ссылки внутри какого-либо шаблона:

html
<a href="{% url 'index' %}">Home</a>.

Примечание: Мы могли бы, конечно, жёстко указать прямую ссылку (то есть, <a href="/catalog/">Home</a>), но тогда, если мы изменим адрес нашей домашней страницы (например на /catalog/index), то данные ссылки перестанут корректно работать. Применение "обратного" url-преобразования более гибкий и эффективный подход!

Отображения (на основе функций)

Отображение является функцией, которая обрабатывает HTTP-запрос, получает данные из базы данных (при необходимости), которые применяются для генерации страницы HTML. Затем функция отображения возвращает сгенерированную страницу пользователю в виде HTTP-ответа. В нашем случае, индексная функция демонстрирует этот процесс — она получает информацию о количестве записей Book, BookInstance, доступности BookInstance, а также записи Author из базы данных, затем передаёт эти записи в шаблон страницы, генерирует страницу и передаёт её пользователю (клиенту пользователя, например браузеру).

Откройте catalog/views.py и отметьте для себя, что данный файл уже импортирует функцию render() - функцию, которая генерирует HTML-файлы при помощи шаблонов страниц и соответствующих данных.

python
from django.shortcuts import render

# Создайте ваше отображение здесь

Скопируйте следующий код в нижнюю часть файла. Первая строка импортирует классы модели, которые мы будем использовать для доступа к данным во всех наших функциях (позже и классах) отображения.

python
from .models import Book, Author, BookInstance, Genre

def index(request):
    """
    Функция отображения для домашней страницы сайта.
    """
    # Генерация "количеств" некоторых главных объектов
    num_books=Book.objects.all().count()
    num_instances=BookInstance.objects.all().count()
    # Доступные книги (статус = 'a')
    num_instances_available=BookInstance.objects.filter(status__exact='a').count()
    num_authors=Author.objects.count()  # Метод 'all()' применён по умолчанию.

    # Отрисовка HTML-шаблона index.html с данными внутри
    # переменной контекста context
    return render(
        request,
        'index.html',
        context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors},
    )

Первая часть функции отображения получает количество записей при помощи вызова функции objects.all() у атрибута objects, доступного для всех классов моделей. Похожим образом мы получаем список объектов BookInstance, которые имеют статус 'a' (Доступно). Вы можете найти дополнительную информацию о работе с моделями в предыдущей части руководства (Руководство часть 3: Применение моделей > Поиск записей).

В конце функции index вызывается функция render(), которая, в качестве ответа, создаёт и возвращает страницу HTML (эта функция "оборачивает" вызовы нескольких функций, тем самым существенно упрощая процесс разработки). В качестве параметров ей передаются объект request (типа HttpRequest), шаблон HTML-страницы с метками (placeholders), которые будут замещены данными, а также переменной context (словарь Python, который содержит данные, которые и будут замещать метки в шаблоне).

В следующем разделе мы более подробно поговорим о шаблонах и переменной контекста. Давайте создадим наш шаблон, чтобы показать уже что-нибудь пользователю!

Шаблон

Шаблон это текстовый файл, который определяет структуру и расположение данных в файле, кроме того, в нем размещают специальные метки (placeholders), которые используются для показа реального содержимого, то есть данных. По умолчанию Django ищет файлы шаблонов в директории с именем 'templates' внутри вашего приложения. Например, внутри индексной функции отображения, которую мы только что создали, вызов render() будет пытаться найти файл /locallibrary/catalog/templates/index.html и в случае неудачи сгенерирует ошибку о том, что файл не найден. Вы можете увидеть данную ошибку, если вы сохраните предыдущие изменения, затем перейдёте в браузер и наберёте в адресной строке 127.0.0.1:8000. В результате, в окно браузера будет выведено сообщение об ошибке "TemplateDoesNotExist at /catalog/" и некоторая другая информация.

Примечание: На самом деле, в зависимости от настроек проекта, Django просматривает несколько мест в поисках шаблона (поиск в директории приложения осуществляется по умолчанию!). Вы можете найти больше информации о шаблонах и форматах, которые они поддерживают, перейдя по ссылке Шаблоны (Django docs).

Расширение шаблонов

Шаблон главной страницы нашего сайта должен соответствовать стандарту разметки HTML для разделов head и body, кроме того иметь разделы для навигации (на другие страницы, которые мы создадим позже) и показа некоторого вводного текста. Большая часть данной структуры будет одинаковой для всех страниц нашего сайта. Таким образом, чтобы избежать копирования одной и той же информации, язык создания шаблонов Django позволяет вам объявить базовый шаблон, а затем расширить его, замещая только те части, которые являются специфическими для каждой страницы.

Например, базовый шаблон base_generic.html может выглядеть как показано ниже. Как вы видите, этот файл содержит некоторую "общую" структуру HTML, разделы для заголовка, панель навигации и содержимое, отмеченное тэгами шаблона block и endblock (показано жирным). Данные блоки могут быть пустыми, или иметь содержимое, которое будет использоваться "по умолчанию" всеми страницами-наследниками.

Примечание: Тэги шаблона подобны функциям, которые могут применяться для создания циклов по спискам, выполнять условные операции и так далее. Кроме тэгов, язык шаблона позволяет использовать переменные (которые передаются в шаблон из отображения), а также шаблонные фильтры, которые переформатируют переменные (например, переводят строку в нижний регистр).

django
<!doctype html>
<html lang="en">
<head>
  {% block title %}<title>Local Library</title>{% endblock %}
</head>

<body>
  {% block sidebar %}<!-- insert default navigation text for every page -->{% endblock %}
  {% block content %}<!-- default content text (typically empty) -->{% endblock %}
</body>
</html>

Когда мы определяем шаблон для конкретного отображения, то в первую очередь мы объявляем базовый шаблон (при помощи тэга extends — смотрите код в следующем фрагменте). Если имеются блоки в базовом шаблоне, которые мы хотим заместить, тогда в нашем текущем шаблоне мы объявляем block/endblock и указываем соответствующее имя блока.

Например фрагмент кода, показанный ниже, демонстрирует применение тэга extends и переопределяет блок с именем content. Окончательный код HTML будет содержать все структуры базового файла шаблона (включая содержимое по умолчанию, которое мы указали в блоке title) и код блока content, который мы разместили в текущем файле шаблона.

django
{% extends "base_generic.html" %}

{% block content %}
  <h1>Local Library Home</h1>
  <p>
    Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a
    tutorial example on the Mozilla Developer Network.
  </p>
{% endblock %}

Базовый шаблон сайта LocalLibrary

Базовый шаблон, который мы планируем использовать для сайта LocalLibrary, представлен ниже. Как вы видите, данный фрагмент содержит HTML код и объявляет следующие блоки title, sidebar и content. Мы добавили заголовок по умолчанию (который, возможно, мы захотим изменить), а также боковую панель навигации, содержащей ссылки на списки всех книг и авторов (панель навигации, мы, вероятно, не будем менять/замещать, но, тем не менее, добавив этот блок, мы оставим для себя такую возможность).

Примечание: Во фрагменте мы используем два дополнительных шаблонных тега: url и load static. Они будут описаны в следующих разделах.

Создайте новый файл — /locallibrary/catalog/templates/base_generic.html — и добавьте в него следующее содержимое:

django
<!doctype html>
<html lang="en">
  <head>
    {% block title %}<title>Local Library</title>{% endblock %}
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="stylesheet"
      href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

    <!-- Добавление дополнительного статического CSS файла -->
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/styles.css' %}" />
  </head>

  <body>
    <div class="container-fluid">
      <div class="row">
        <div class="col-sm-2">
          {% block sidebar %}
            <ul class="sidebar-nav">
              <li><a href="{% url 'index' %}">Home</a></li>
              <li><a href="">All books</a></li>
              <li><a href="">All authors</a></li>
            </ul>
          {% endblock %}
        </div>
        <div class="col-sm-10 ">{% block content %}{% endblock %}</div>
      </div>
    </div>
  </body>
</html>

Шаблон использует (и включает в себя) JavaScript и CSS от Bootstrap для лучшего размещения элементов и формирования внешнего вида HTML страницы. Применение Bootstrap, или любого другого фреймворка для клиентской части сайта, является довольно продуктивным способом повышения привлекательности страницы, в том числе, это учитывает возможность запроса и показа сайта с устройств, с различными разрешениями экрана, а кроме того, это позволяет нам повысить уровень взаимодействия с пользователем — мы направим большую часть своих усилий на серверную часть нашего сайта!

Базовый шаблон ссылается на локальный файл css (styles.css), который предоставляет дополнительные стили. Создайте /locallibrary/catalog/static/css/styles.css и добавьте в него следующее содержимое:

css
.sidebar-nav {
  margin-top: 20px;
  padding: 0;
  list-style: none;
}

Индексный шаблон (шаблон главной страницы сайта)

Создайте файл HTML /locallibrary/catalog/templates/index.html и скопируйте в него код, указанный ниже. Как вы наверное заметили, в первой строке мы расширяем наш базовый шаблон, а затем замещаем содержимое блока content, базового шаблона, новым содержимым текущего шаблона.

django
{% extends "base_generic.html" %}

{% block content %}
<h1>Local Library Home</h1>

  <p>Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.</p>

<h2>Dynamic content</h2>

  <p>The library has the following record counts:</p>
  <ul>
    <li><strong>Books:</strong> {{ num_books }}</li>
    <li><strong>Copies:</strong> {{ num_instances }}</li>
    <li><strong>Copies available:</strong> {{ num_instances_available }}</li>
    <li><strong>Authors:</strong> {{ num_authors }}</li>
  </ul>

{% endblock %}

В данном фрагменте, в разделе Динамическое содержимое, мы объявили метки (шаблонные переменные) для информации, которую мы получаем из соответствующего отображения. Данные переменные объявляются при помощи "двойных фигурных скобок" (в предыдущем фрагменте выделено жирным).

Примечание: Переменные шаблона заключаются в двойные фигурные скобки ({{ num_books }}) , а тэги шаблона (функции шаблона), помещаются в одинарные фигурные скобки со знаками процента ({% extends "base_generic.html" %}).

Важно отметить, что данные переменные имеют имена, соответствующие именам передаваемых ключей из словаря переменной context, которая, в свою очередь, передаётся из отображения, во время вызова функции render() (смотри ниже). При отрисовке шаблона, вместо этих ключей будут подставлены, соответствующие им, значения.

python
return render(
    request,
    'index.html',
     context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors},
)

Ссылка на статические файлы их шаблонов

Любой ваш проект с большой вероятностью будет использовать статические ресурсы, включая JavaScript, CSS и изображения. В связи с тем, что расположение этих файлов может быть неизвестно (или может измениться), Django позволяет вам в шаблоне указать относительное расположение данных файлов при помощи глобального значения STATIC_URL (по умолчанию, значение параметра STATIC_URL установлено в '/static/', но вы можете выбрать любое другое значение, указав, например, сетевой ресурс, или что-то ещё).

Внутри шаблона вы вызываете функцию (тэг) load, которая загружает статическую библиотеку "static" (как показано ниже). После того как статическая библиотека загружена, вы можете использовать тэг шаблона static, который указывает относительный путь URL к интересующему вас файлу.

django
<!-- Добавляем дополнительный статический CSS-файл -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}" />

Тем же способом вы можете загрузить нужное изображение. Например:

django
{% load static %}
<img
  src="{% static 'catalog/images/local_library_model_uml.png' %}"
  alt="My image"
  style="width:555px;height:540px;" />

Примечание: Фрагменты выше указывают пути расположения файлов, но Django не использует их по умолчанию. В процессе разработки сервер использует значения, указанные в глобальном файле URL-преобразований (/locallibrary/locallibrary/urls.py), который мы создали в части создание скелета сайта. В дальнейшем, в продакшене, вам нужно будет уточнить параметры расположения статических файлов. Мы вернёмся к этому позже.

Для получения более подробной информации о работе со статическими файлами обратитесь к документации по ссылке Управление статическими файлами (Django docs).

Построение URL-адресов

Базовый шаблон, указанный выше, вводит тэг url.

python
<li><a href="{% url 'index' %}">Home</a></li>

Данный тэг с именем url(), ищет в файле urls.py связанное значение переменной, указанной в качестве её параметра 'index', а затем возвращает URL, который вы можете использовать для ссылки на соответствующие ресурсы.

Как теперь все это выглядит?

На данный момент мы должны были сделать всё что необходимо, для того, чтобы показать главную страницу нашего сайта. Запустите сервер (python3 manage.py runserver) и введите в ваш браузер адрес http://127.0.0.1:8000/. Если все настроено как надо, то ваш сайт должен выглядеть как показано на следующей картинке.

Index page for LocalLibrary website

Примечание: На данном этапе вы не сможете воспользоваться ссылками на страницы All books и All authors, потому что url-адреса, отображения и шаблоны для данных страниц не созданы (мы просто объявили метки для соответствующих ссылок в базовом шаблоне base_generic.html).

Проверьте себя

А теперь парочка заданий, чтобы проверить, насколько вы усвоили работу с запросами к моделям базы данных, взаимодействия с отображениями и шаблонами.

  1. В главном файле шаблона (base_generic.html) есть блок title. Переопределите этот блок в индексном шаблоне (index.html) и задайте новый заголовок для этой страницы.
  2. Модифицируйте функцию отображения таким образом, чтобы получать из базы данных количество жанров и количество книг, которые содержат в своих заголовках какое-либо слово (без учёта регистра), а затем передайте эти значения в шаблон.

Итог

Мы создали домашнюю страницу для нашего сайта — HTML страница, которая показывает количество некоторых записей из базы данных и содержит ссылки на другие "все-ещё-будут-созданы" страницы. Кроме того, мы изучили большое количество базовой информации об url-преобразованиях, отображениях, запросах к базе данных, используя наши модели, передачу информации из отображений в шаблоны, кроме того, создание и расширение шаблонов.

В следующей части, при помощи наших новых знаний, мы создадим ещё четыре страницы.

Смотрите также