Learn web development

Руководство часть 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-преобразование

Мы создали базовый файл /catalog/urls.py для нашего приложения catalog, когда мы создавали скелет сайта. URL-адреса данного приложения были включены в проект бутем преобразования catalog/ (в корневом файле urls.py сайта), таким образом URL-адреса данного приложения всегда начинаются со строки  catalog/.

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

urlpatterns = [
    url(r'^$', views.index, name='index'),
]

Эта функция url() определяет URL-паттерн (r'^$') и функцию отображения, которая будет вызвана, если введенный адрес будет соответствать данному паттерну (views.index — это функция с именем index() в views.py). URL-паттерн это Регулярное выражение Python(РВ). Мы поговрим чуть более подробно о них далее в данном руководстве, а для данного случая, все что вам надо знать, это то, что данное РВ состоит из символов ^$ и соответствует пустой строке (^ - маркер начала строки, а $ - маркер конца строки). 

Примечание:  Перейдем в корневой файл /locallibrary/locallibrary/urls.py 

urlpatterns += [
    url(r'^catalog/', include('catalog.urls')),
]

Регулярное выражение в данном случае не содердит $ (маркер конца строки), но включает в себя завершающий слэш. В тот момент, когда Django  встретит функцию include() (django.conf.urls.include()), он обрежет все, начиная с данной точки (слэша) и отправит оставшуются часть (до слеша) в URLconf для дальнейшей обработки.

 Соответственно, наш URL-адрес будет иметь вид catalog/ + <пустая строка> ( /catalog/ подразумевается, так как применяется метод include() ). То есть, наша первая функция отображения будет вызвана, если мы получим HTTP-запрос с адресом равным /catalog/.

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

<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-файлы при помощи шаблонов страниц и соответствующих данных. 

from django.shortcuts import render

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

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

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 (показано жирным). Данные блоки могут быть пустыми, или иметь содержимое, которое будет использоваться "по умолчанию" всеми страницами-наследниками.

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

<!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, который мы разместили в текущем файле шаблона.

{% 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 — и добавьте в него следующее содержимое:

<!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 и добавьте в него следующее содержимое:

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

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

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

{% 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() (смотри ниже). При отрисовке шаблона, вместо этих ключей будут подставлены, соответствующие им, значения.  

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 к интересующему вас файлу.

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

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

{% 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.

<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. В главном файле шаблона (индексном шаблоне) объявите новый блок заголовка, соответствующий текущей странице.
  2. Модифицируйте функцию отображения таким образом, чтобы получать из базы данных количество жанров и количество книг, которые содержат в своих заголовках какое-либо слово (без учета регистра), а затем передайте эти значения в шаблон.

Итог

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

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

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

Метки документа и участники

 Внесли вклад в эту страницу: al+chernin
 Обновлялась последний раз: al+chernin,