diff options
Diffstat (limited to 'files/ru/learn/server-side/django/generic_views/index.html')
-rw-r--r-- | files/ru/learn/server-side/django/generic_views/index.html | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/files/ru/learn/server-side/django/generic_views/index.html b/files/ru/learn/server-side/django/generic_views/index.html new file mode 100644 index 0000000000..7b1bf6f08c --- /dev/null +++ b/files/ru/learn/server-side/django/generic_views/index.html @@ -0,0 +1,605 @@ +--- +title: 'Руководство часть 6: Отображение списков и детальной информации' +slug: Learn/Server-side/Django/Generic_views +tags: + - django + - Для начинающих + - Отображения django + - Руководство + - Шаблоны django +translation_of: Learn/Server-side/Django/Generic_views +--- +<div>{{LearnSidebar}}<br> +{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">Данная часть расширяет наш сайт <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, добавляя в него списки и страницы, путем предоставления подробной информации о книгах и авторах. В текущей части мы подробно изучим обобщенные базовые классы отображения и покажем как они могут существенно сократить количество кода, который вы должны были бы написать в обычной ситуации. Кроме того, мы более подробно рассмотрим управление и настройки URL-адресов, показывая как выполнить простое сопоставление какой-либо строки паттерну регулярного выражения.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Завершить все предыдущие части руководства, включая <a href="/en-US/docs/Learn/Server-side/Django/Home_page">Руководство Django Часть 5: Создание домашней страницы</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понимать где и как применять обобщенные базовые классы отображения, и как применять паттерны URL-адресов для передачи информации в отображения.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В данном руководстве мы завершим первую версию сайта <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, с помощью добавления страницы перечисления и подробной информации о книгах и авторах (или, если быть более точными, мы покажем как вам реализовать соответствующие страницы для книг, а для авторов вы сможете сделать их самостоятельно!)</p> + +<p>Данный процесс похож на создание главной страницы сайта, который мы показывали в предыдущей части руководства. Нам все также надо создать URL-преобразования, отображения и шаблоны страниц. Основным отличием будет то, что для страниц подробной информации перед нами встанет дополнительная задача получения информации из паттерна URL-адреса и передачи ее отображению. Для этих страниц мы собираемся продемонстрировать совершенно другой тип отображения, основанный на применении обобщеных классов отображения списка и детальной информации о записи. Это может существенно сократить количество кода, необходимого для отображения и сделает его (код) более простым для написания и поддержки.</p> + +<p>Завершающая часть данного руководства будет посвещена демонстрации постраничного показа ваших данных (pagination) при применении обобщенного класса отображения списка.</p> + +<h2 id="Страница_со_списком_книг">Страница со списком книг</h2> + +<p>Страница со списком книг показывает все книги в наличии и будет доступна по адресу: <code>catalog/books/</code>. Эта страница для каждой записи выводит заголовок и автора, при этом каждый заголовок является гиперссылкой на соответствующую страницу подробной информации о книге. Данная страница будет иметь ту же структуру и навигацию как и все остальные страницы сайта, таким образом мы сможем расширить базовый шаблон сайта (<strong>base_generic.html</strong>), который мы создали в предыдущей части руководства.</p> + +<h3 id="Преобразования_URL-адресов">Преобразования URL-адресов</h3> + +<p>Откройте файл <strong>/catalog/urls.py</strong> и скопируйте в него строки, выделенные жирным внизу. Практически также как и для главной страницы сайта, данная функция <code>url()</code> определяет регулярное выражение (<strong>r'^books/$'</strong>), связывающее URL-адрес с функцией отображения (<code>views.BookListView.as_view()</code> ), которая будет вызвана, если URL-адрес будет соответствовать паттерну РВ. Кроме того, определяется имя для данного сопоставления.</p> + +<pre class="brush: python">from django.urls import path +from . import views +<strong>from django.conf.urls import url</strong> + +urlpatterns = [ + url(r'^$', views.index, name='index'), +<strong> url(r'^books/$', views.BookListView.as_view(), name='books'), +</strong>]</pre> + +<p>Данный паттерн РВ сопоставления URL-адреса полностью соответствует строке <code>books/</code> (<code>^</code> является маркером начала строки, а <code>$</code> - маркер конца строки). Как было отмечено в предыдущей части руководства, URL-адрес уже должен содержать <code>/catalog</code>, таким образом полный адрес, на самом деле, имеет вид : <code>/catalog/books/</code>.</p> + +<p>Функция отображения имеет другой формат, чем ранее — это связано с тем, что данное отображение реализуется через класс. Мы будем наследоваться от существующей общей функции из <strong>view</strong>, которая уже делает большую часть того, что мы хотим, что нам и нужно, вместо того, чтобы писать свою собственную функцию во <strong>view</strong> с нуля.</p> + +<p>При использовании обобщенных классов отображения в Django мы получаем доступ к соответствующей функции отображения при помощи вызова метода <code>as_view()</code>. Таким образом выполняется вся работа по созданию экземпляра класса и гарантируется вызов правильных методов для входящих HTTP-запросов.</p> + +<h3 id="Отображение_(на_основе_базового_класса)">Отображение (на основе базового класса)</h3> + +<p>Мы могли бы достаточно просто реализовать отображение списка книг при помощи обычной функции (также, как мы сделали это для главной страницы сайта), которая должны была бы выполнить запрос получения всех книг из базы данных, затем вызвать функцию <code>render()</code>, в которую передать данный список, в соответствующий шаблон страницы. Тем не менее, вместо это мы будем использовать обобщенный класс отображения списка — класс, который наследуется от существующего отображения (ListView). Поскольку обобщенный класс уже реализует большую часть того, что нам нужно, и следуя лучшим практикам Django, мы сможем создать более эффективный список при помощи меньшего количества кода, меньшего количества повторений и гораздо лучшей поддержкой.</p> + +<p>Откройте <strong>catalog/views.py</strong> и скопируйте следующий код, в нижнюю часть данного файла:</p> + +<pre class="brush: python">from django.views import generic + +class BookListView(generic.ListView): + model = Book</pre> + +<p>Это всё! Обобщенное отображение выполнит запрос к базе данных, получит все записи заданной модели (<code>Book</code>), затем отрендерит (отрисует) соответствующий шаблон, расположенный в <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> (который мы создадим позже). Внутри данного шаблона вы можете получить доступ к списку книг при помощи переменной шаблона <code>object_list</code> ИЛИ <code>book_list</code> (если обобщить, то "<code><em>the_model_name</em>_list</code>").</p> + +<div class="note"> +<p><strong>Примечание</strong>: Этот, выглядящий странно, путь к файлу шаблона не является опечаткой — обобщенное отображение ищет файл шаблона <code>/<em>application_name</em>/<em>the_model_name</em>_list.html</code> (<code>catalog/book_list.html</code>, в данном случае) внутри директории приложения <code>/<em>application_name</em>/templates/</code> (у нас - <code>/catalog/templates/)</code>.</p> +</div> + +<p>Вы можете использовать атрибуты для того, чтобы изменить поведение по умолчанию. Например, вы могли бы указать другой файл шаблона, например, если в вашем распоряжении имеется несколько отображений, которые используют одну и ту же модель, или вам позарез захотелось бы использовать другое имя переменной шаблона, если <code>book_list</code> не является интуитивно понятным. Возможно, наиболее полезным вариантом является изменение/отфильтрование результата запроса к базе данных — таким образом, вместо перечисления всех книг вы могли бы показывать 5 наиболее популярных.</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + context_object_name = 'my_book_list' # ваше собственное имя переменной контекста в шаблоне + queryset = Book.objects.filter(title__icontains='war')[:5] # Получение 5 книг, содержащих слово 'war' в заголовке + template_name = 'books/my_arbitrary_template_name_list.html' # Определение имени вашего шаблона и его расположения</pre> + +<h4 id="Переопределение_методов_в_классах_отображения">Переопределение методов в классах отображения</h4> + +<p>Пока что вам не приходилось этого делать, но у вас имеется возможность переопределять некоторые методы класса отображения.</p> + +<p>Например, мы можем переопределить метод получения списка всех записей <code>get_queryset()</code>. Данный подход является более гибким, чем использование атрибута <code>queryset</code>, как мы сделали в предыдущем фрагменте кода (хотя, в данном случае и нет никакой разницы):</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + + def get_queryset(self): + return Book.objects.filter(title__icontains='war')[:5] # Получить 5 книг, содержащих 'war' в заголовке +</pre> + +<p>Мы также могли бы переопределить метод <code>get_context_data()</code> для того, чтобы в контексте (в переменной контекста) передавать шаблону дополнительные переменные (например, список книг передается по умолчанию). Фрагмент, представленный ниже, показывает как добавить переменную с именем "some_data" в контекст (затем она будет доступна как переменная шаблона).</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + + def get_context_data(self, **kwargs): + # В первую очередь получаем базовую реализацию контекста + context = super(BookListView, self).get_context_data(**kwargs) + # Добавляем новую переменную к контексту и иниуиализируем ее некоторым значением + context['some_data'] = 'This is just some data' + return context</pre> + +<p>В процессе выполнения всего этого важно придерживаться определенной последовательности действий:</p> + +<ul> + <li>В первую очередь - получить существующий контекст из нашего суперкласса.</li> + <li>Затем добавить в контекст новую информацию.</li> + <li>Затем вернуть новый (обновленный) контекст.</li> +</ul> + +<div class="note"> +<p><strong>Примечание</strong>: Посмотрите <a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/">Встроенные обобщенные классы отображения</a> (Django docs) для ознакомления с большим количеством примеров того, что вы могли бы сделать.</p> +</div> + +<h3 id="Создание_шаблона_Отображения_Списка">Создание шаблона Отображения Списка</h3> + +<p>Создайте HTML-файл <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> и скопируйте в него текст, указанный ниже. Как было отмечено ранее, это файл шаблона по умолчанию, который будет "искать" обобщенный класс отображения списка (для модели с именем <code>Book</code> в приложении с именем <code>catalog</code>).</p> + +<p>Шаблоны для обобщенных отображений такие же как все остальные шаблоны (хотя, естественно, передаваемые в них контекст, или информация могут отличаться). Так же как и с нашим шаблоном для главной страницы, в первой строке мы расширяем наш базовый шаблон, а затем определяем и замещаем блок с именем <code>content</code>.</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Book List</h1> + + <strong>{% if book_list %}</strong> + <ul> + + {% for book in book_list %} + <li> + <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) + </li> + {% endfor %} + + </ul> + <strong>{% else %}</strong> + <p>There are no books in the library.</p> + <strong>{% endif %} </strong> +{% endblock %}</pre> + +<p>По умолчанию отображение передает контекст (список книг) как <code>object_list</code> и <code>book_list</code> (синонимы; оба варианта будут работать).</p> + +<h4 id="Условные_ветвления">Условные ветвления</h4> + +<p>Мы применяем теги шаблона <code><a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#if">if</a></code>, <code>else</code> и <code>endif</code> для того, чтобы проверить определена ли переменная <code>book_list</code> и содержит ли она данные. Если список НЕ пуст, тогда мы выполняем итерации по списку книг. Если список пуст (<code>else</code>-случай) тогда мы показываем текст, поясняющий, что в наличии нет книг.</p> + +<pre class="brush: html"><strong>{% if book_list %}</strong> + <!-- здесь наш код "бежит" по списку книг --> +<strong>{% else %}</strong> + <p>В библиотеке книг нет.</p> +<strong>{% endif %}</strong> +</pre> + +<p>В данном фрагменте проверяется только одно условие, но вы можете протестировать другие варианты при помощи тэга шаблона <code>elif</code> (например, <code>{% elif var2 %}</code> ). Для дополнительной информации по данной теме смотрите: <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#if">if</a>, <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#ifequal-and-ifnotequal">ifequal/ifnotequal</a> и <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#ifchanged">ifchanged</a> в главе <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins">Встроенные тэги и фильтры шаблона</a> (Django Docs).</p> + +<h4 id="Цикл_For">Цикл For</h4> + +<p>Шаблон использует тэги <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#for">for</a> и <code>endfor</code> для того, чтобы "пробежаться" по списку книг, как показано ниже. На каждой итерации (каждом цикле) в переменную шаблона <code>book</code> передается информация текущего эелемента списка.</p> + +<pre class="brush: html">{% for <strong>book</strong> in book_list %} + <li> <!-- здесь код, который использует информацию из каждого элемента <strong>book </strong>списка--> </li> +{% endfor %} +</pre> + +<p>Мы не применяем здесь, но внутри каждого цикла Django создает переменные, которые вы можете использовать при итерации. Например, вы можете проверять переменную <code>forloop.last</code> (указывает на последнюю итерацию в цикле) для выполнения каких-либо завершающих действий для данного цикла.</p> + +<h4 id="Доступ_к_переменным">Доступ к переменным</h4> + +<p>Код внутри цикла создает экземпляр для каждой книги из списка, при помощи которой показывается заголовок (как ссылка на "скоро-будет-сделано" подробное отображение) и автора книги.</p> + +<pre class="brush: html"><a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) +</pre> + +<p>Мы получаем доступ к <em>полям</em> соответствующей записи о книге при помощи "дот-нотации", то есть через точку (например, <code>book.title</code> и <code>book.author</code>), где текст, который идет после <code>book</code>, является именем поля (так, как определено в модели).</p> + +<p>Кроме того, внутри нашего шаблона, мы можем вызывать <em>функции</em> модели — в данном случае, мы вызываем <code>Book.get_absolute_url()</code> для получения URL-адреса, который мы используем для показа детальной информации о книге. Данный вызов работает только для функции у которой нет аргументов (в шаблоне не существует возможности передать аргументы в функцию!)</p> + +<div class="note"> +<p><strong>Примечание</strong>: Мы должны быть достаточно осмотрительными для того, чтобы избегать "сторонних эффектов" когда мы вызываем функции из шаблона. В данном случае мы просто получаем URL-адрес, но функции могут делать все что угодно — мы не хотели бы "убить" наша базу данных (например) просто отрендеривая наш шаблон!</p> +</div> + +<h4 id="Обновление_базового_шаблона">Обновление базового шаблона</h4> + +<p>Откройте файл базового шаблона (<strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>) и вставьте <strong>{% url 'books' %} </strong> в URL-ссылку для пункта <strong>All books</strong>, как показано ниже. Тем самым, мы создали "переход" на страницу с книгами (теперь мы можем смело это сделать, поскольку у нас имеется соответствующее "книжное" url-преобразование).</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +<strong><li><a href="{% url 'books' %}">All books</a></li></strong> +<li><a href="">All authors</a></li></pre> + +<h3 id="Как_же_теперь_все_это_выглядит">Как же теперь все это выглядит?</h3> + +<p>Пока что у вас нет возможности создать список книг, потому что мы не учли еще необходимые зависимости — преобразование URL-адреса для страниц с подробной информации о книге, которое необходимо для ссылок на отдельные книги. Мы покажем страницы со списком и подробной информацией о книге после следующего раздела.</p> + +<h2 id="Страница_с_подробной_информацией_о_книге">Страница с подробной информацией о книге</h2> + +<p>Доступ к странице с подробной информацией о книге осуществляется при помощи URL-адреса <code>catalog/book/<em><id></em></code> (где <code><em><id></em></code> является первичным ключом для данной книги). В дополнение к полям модели <code>Book</code> (автор, краткое содержание, ISBN, язык и жанр), также мы перечислим детали доступных экземпляров книги (<code>BookInstances</code>) включая их статус, ожидаемую дату возврата, штамп (imprint) и id. Это должно позволить нашим читателям не просто узнать о книге, но также убедиться, имеется ли она в наличии и/или когда будет доступна.</p> + +<h3 id="URL-преобразования">URL-преобразования</h3> + +<p>Откройте <strong>/catalog/urls.py</strong> и добавьте '<strong>book-detail</strong>' URL-преобразование, отмеченное жирным в следующем фрагменте. Эта функция <code>url()</code> определяет паттерн, связанный с обобщенным классом отображения детальной информации, а также имя для данной связи.</p> + +<pre class="brush: python">from django.urls import path +from . import views +<strong>from django.conf.urls import url</strong> + +urlpatterns = [ + url(r'^$', views.index, name='index'), + url(r'^books/$', views.BookListView.as_view(), name='books'), + <strong>url(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),</strong> +]</pre> + +<p>В отличие от предыдущих преобразований, в данном случае мы применяем наше регулярное выражение (РВ) для сопоставления "настоящего паттерна", а не просто строки. Данное РВ сопоставляет любой URL-адрес, который начинается с <code>book/</code>, за которым до конца строки (до маркера конца строки - $) следуют одна, или более <em>цифр</em>. В процессе выполнения данного преобразования, оно "захватывает" цифры и передает их в функцию отображения как параметр с именем <code>pk</code>.</p> + +<div class="note"> +<p><strong>Примечание</strong>: как было отмечено ранее, наш преоразуемый URL-адрес в реальности выглядит вот так <code>catalog/book/<digits></code> (потому что мы находимся в приложении <strong>catalog</strong>, то подразумевается каталог <code>/catalog/</code>).</p> +</div> + +<div class="warning"> +<p><strong>Важно</strong>: Обобщенный класс отображения подробной информации ожидает получить параметр с именем pk. Если вы пишете свою собственную функцию отображения, то тогда вы можете использовать параметр с любым именем, который пожелаете, или вообще передавать информацию в безымянном аргументе.</p> +</div> + +<h4 id="Отдельный_пример_с_регулярными_выражениями">Отдельный пример с регулярными выражениями</h4> + +<p>Паттерны <a href="https://docs.python.org/3/library/re.html">регулярного выражения</a> является невероятно мощным инструментом преобразования. Пока что, мы не очень много говорили о них, поскольку мы сопоставляли URL-адреса с простыми строками (а не паттернами), и потому что они не интуитивны и пугающи для начинающих.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Без паники! Мы будем рассматривать и использовать достаточно простые паттерны и при этом хорошо задокументированные!</p> +</div> + +<p>В первую очередь вы должны знать что обычно регулярные выражения объявляются при помощи строкового литерала (то есть, они заключены в кавычки: <strong>r'<ваше регулярное выражение>'</strong>).</p> + +<p>Главными элементами синтаксиса объявления паттерна, который вы должны знать, являются:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Символ</th> + <th scope="col">Значение</th> + </tr> + </thead> + <tbody> + <tr> + <td>^</td> + <td>Соответствует началу строки</td> + </tr> + <tr> + <td>$</td> + <td>Соответствует концу строки</td> + </tr> + <tr> + <td>\d</td> + <td>Соответствует цифре (0, 1, 2, ... 9)</td> + </tr> + <tr> + <td>\w</td> + <td>Соответствует любому символу из алфавита в верхнем- или нижнем- регистре, цифре, или символу подчеркивания (_)</td> + </tr> + <tr> + <td>+</td> + <td>Соответствует <strong>одному, или более</strong> предыдущему символу. Например, для соответствия <strong>одной, или более</strong> цифре вы должны использовать <code>\d+</code>. Для <strong>одного и более</strong> символа "a", вы можете использовать <code>a+</code></td> + </tr> + <tr> + <td>*</td> + <td>Соответствует отсутствию вообще, или присутствию <strong>одного, или более</strong> предыдущему символу. Например, для соответствия "ничему", или слову (то есть, любому символу) вы можете использовать <code>\w*</code></td> + </tr> + <tr> + <td>( )</td> + <td>Захват части паттерна внутри скобок. Любое захваченное значение будет передано отображению как безымянный параметр (если захватывается множество паттернов, то соответствующие параметры будут поставляться в порядке их объявления).</td> + </tr> + <tr> + <td>(?P<name>...)</td> + <td>Захват части паттерна (обозначеного через ...) как именованной переменной (в данном случае <name>). Захваченные значения передаются в отображение с определенным именем. Таким образом, ваше отображение должно объявить аргумент с тем же самым именем!</td> + </tr> + <tr> + <td>[ ]</td> + <td>Соответствует одному символу из множества. Например, [abc] будет соответствовать либо 'a', или 'b', или 'c'. [-\w] будет соответствовать либо символу '-' , или любому другому словарному символу.</td> + </tr> + </tbody> +</table> + +<p>Большинство других символов могут быть заданы буквально!</p> + +<p>Давайте рассмотрим несколько реальных примеров паттернов:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Паттерн</th> + <th scope="col">Описание</th> + </tr> + </thead> + <tbody> + <tr> + <td><strong>r'^book/(?P<pk>\d+)$'</strong></td> + <td> + <p>Это РВ применяется в нашем url-преобразовании. Оно соответствует строке, которая начинается с <code>book/</code> (<strong>^book/</strong>), затем имеет одну, или более цифр (<code>\d+</code>), а затем завершается (цифрой и только цифрой).</p> + + <p>Оно также захватывает все цифры <strong>(?P<pk>\d+)</strong> и передает их в отображение, в параметре с именем 'pk'. <strong>Захваченные значения всегда передаются как строка!</strong></p> + + <p>Например, данному паттерну должна соответствовать следующая строка <code>book/1234</code> , которая отправляет переменную <code>pk='1234'</code> в отображение.</p> + </td> + </tr> + <tr> + <td><strong>r'^book/(\d+)$'</strong></td> + <td>Этот паттерн соответствует тем же самым URL-адресам как и в предыдущем случае. Захваченная информация будет отправлена в отображение как безымянный параметр.</td> + </tr> + <tr> + <td><strong>r'^book/(?P<stub>[-\w]+)$'</strong></td> + <td> + <p>Данный паттерн соответствует строке, которая начинается с <code>book/</code> (<strong>^book/</strong>), затем идут один, или более символов либо '-', или словарные символы (<strong>[-\w]+</strong>), а затем завершается. Он также захватывает данное множество символов и передает их в отображение в параметре с именем 'stub'.</p> + + <p>Это довольно типичный паттерн для "стаба". Стабы являются дружественными URL-адресами - первичными ключами для данных. Вы могли бы применить стаб, если вы захотели бы, чтобы URL-адрес вашей книги был более информативным. Например, <code>/catalog/book/the-secret-garden</code>, выглядит немного лучше чем <code>/catalog/book/33</code>.</p> + </td> + </tr> + </tbody> +</table> + +<p>Вы можете захватить (указать) несколько паттернов в одном преобразовании и, тем самым, закодировать много различной информации в URL-адресе.</p> + +<div class="note"> +<p><strong>Примечание</strong>: В качестве дополнительного задания, рассмотрите возможность того, как вы могли бы закодировать url на список всех книг, вышедших в определенный год, месяц, день и какое РВ (паттерн) должно соответствовать этому.</p> +</div> + +<h4 id="Передача_дополнительных_настроек_в_ваши_преобразования_URL-адресов">Передача дополнительных настроек в ваши преобразования URL-адресов</h4> + +<p>Одной возможностью, которую мы не применяли здесь, но которая могла бы быть вам полезной, является то, что вы можете объявлять и передавать <a href="https://docs.djangoproject.com/en/1.10/topics/http/urls/#views-extra-options">дополнительные настройки</a> в отображения. Данные настройки объявляются как словарь, который вы передаете как третий безымянный аргумент функции <code>url()</code>. Этот способ может быть полезен, если вы хотите воспользоваться тем же самым отображением для нескольких ресурсов и передавать данные для изменения его поведения в каждом отдельном случае (ниже, мы передаем разные имена шаблонов).</p> + +<pre class="brush: python">url(r'^/url/$', views.my_reused_view, {'my_template_name': 'some_path'}, name='aurl'), +url(r'^/anotherurl/$', views.my_reused_view, {'my_template_name': 'another_path'}, name='anotherurl'), + +</pre> + +<div class="note"> +<p><strong>Примечание:</strong> И дополнительные настройки, и именованные захваченные паттерны передаются в отображение как именованные параметры. Если вы используете одинаковое имя и для захваченного паттерна и для дополнительной настройки, то последняя будет отброшена, а в отображение будет передано значение захваченного паттерна. </p> +</div> + +<h3 id="Отображение_(на_основе_класса)">Отображение (на основе класса)</h3> + +<p>Откройте <strong>catalog/views.py</strong>, и скопируйте следующий код в нижнюю часть файла:</p> + +<pre class="brush: python">class BookDetailView(generic.DetailView): + model = Book</pre> + +<p>Это всё! Все что вам надо теперь сделать это создать шаблон с именем <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>, а отображение передаст ему информацию из базы данных для определенной записи <code>Book,</code>выделенной при помощи URL-преобразования. Внутри шаблона вы можете получить доступ к списку книг при помощи переменной с именем <code>object</code> или <code>book</code> (обобщённо "<code><em>the_model_name</em></code>").</p> + +<p>Если у вас имеется необходимость, то вы можете изменить текущий шаблон и/или имя объекта контекста, используемого для ссылки на книгу в шаблоне. Кроме того, вы можете переопределить методы, например, для добавления дополнительной информации к контексту.</p> + +<h4 id="Что_произойдет_если_записи_не_существует">Что произойдет, если записи не существует?</h4> + +<p>Если запрашиваемой записи не существует, тогда обобщенный класс отображения подробной информации автоматически "выкинет" исключение Http404 — в продакшине это приведет к автоматическому отображению страницы с текстом "resource not found" ("ресурс не найден"), которую, конечно же, вы можете настроить по своему усмотрению.</p> + +<p>Просто для иллюстрации идеи как это могло бы работать, мы приведем фрагмент кода, демонстрирующего возможную реализацию отображения в виде функции, если по каким-либо причинам вы не используете отображение на основе обобщенного класса.</p> + +<pre class="brush: python">def book_detail_view(request,pk): + try: + book_id=Book.objects.get(pk=pk) + except Book.DoesNotExist: + raise Http404("Book does not exist") + + #book_id=get_object_or_404(Book, pk=pk) + + return render( + request, + 'catalog/book_detail.html', + context={'book':book_id,} + ) +</pre> + +<p>В первую очередь отображение пытается получить определенную запись о книге из модели. Если ей это не удается, то "выбрасывается" исключение <code>Http404</code>, которое сигнализирует, что данная книга не найдена "not found". Последним шагом является, как обычно, вызов функции <code>render()</code> с именем соответствующего шаблона и данных о книге, передаваемых в параметре с именем <code>context</code> (в виде словаря).</p> + +<div class="note"> +<p><strong>Примечание</strong>: Функция <code>get_object_or_404()</code> (показана закомментированной) является удобным "ярлыком" для генерации исключения <code>Http404</code> если запись не найдена.</p> +</div> + +<h3 id="Создание_шаблона_детальной_информации">Создание шаблона детальной информации</h3> + +<p>Создайте HTML файл <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong> и скопируйте в него содержимое, представленное ниже. Как было указано ранее, это шаблон "по умолчанию" (имя шаблона), который "ожидается"обобщенным классом отображения детальной информации (для модели с именем <code>Book</code> в приложении с именем <code>catalog</code>).</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Title: \{{ book.title }}</h1> + + <p><strong>Author:</strong> <a href="">\{{ book.author }}</a></p> <!-- author detail link not yet defined --> + <p><strong>Summary:</strong> \{{ book.summary }}</p> + <p><strong>ISBN:</strong> \{{ book.isbn }}</p> + <p><strong>Language:</strong> \{{ book.language }}</p> + <p><strong>Genre:</strong> {% for genre in book.genre.all %} \{{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}</p> + + <div style="margin-left:20px;margin-top:20px"> + <h4>Copies</h4> + + {% for copy in book.bookinstance_set.all %} + <hr> + <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'd' %}text-danger{% else %}text-warning{% endif %}">\{{ copy.get_status_display }}</p> + {% if copy.status != 'a' %}<p><strong>Due to be returned:</strong> \{{copy.due_back}}</p>{% endif %} + <p><strong>Imprint:</strong> \{{copy.imprint}}</p> + <p class="text-muted"><strong>Id:</strong> \{{copy.id}}</p> + {% endfor %} + </div> +{% endblock %}</pre> + +<ul> +</ul> + +<div class="note"> +<p>Ссылка на автора в шаблоне содержит пустой URL-адрес, потому что мы еще не создали страницу детальной информации об авторе. Когда это произойдет, вы должны будете обновить данный URL-адрес как указано ниже:</p> + +<pre><a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a> +</pre> +</div> + +<p>Хотя и несколько больше, но почти все в данном шаблоне нам уже встречалось ранее:</p> + +<ul> + <li>Мы расширяем наш базовый шаблон и переопределяем блок <code>content</code>.</li> + <li>Мы используем условие <code>if</code> для показа того, или иного содержимого.</li> + <li>Мы используем циклы <code>for</code> того, чтобы пробежаться по элементам (объектам) в соответствующих списках.</li> + <li>Мы получаем доступ к полям контекста при помощи "дот-нотации" (поскольку мы использовали обобщенный класс отображения детальной информации, то контекст имеет имя <code>book</code>; также можем использовать имя <code>object</code>)</li> +</ul> + +<p>Одной интересной вещью, которую мы не видели ранее, является функция <code>book.bookinstance_set.all()</code>. Данный метод является "автомагически"-сконструированным Django для того, чтобы вернуть множество записей <code>BookInstance</code>, связанных с данной книгой <code>Book</code>.</p> + +<pre class="brush: python">{% for copy in book.bookinstance_set.all %} +<!-- итерации по каждой копии/экземпляру книги --> +{% endfor %}</pre> + +<p>Этот метод создан, потому что вы, на стороне "многим" данной связи, объявили поле <code>ForeignKey</code> (один-ко многим). Поскольку вы ничего не объявили на другой стороне ("один") данной модели (то есть, модель <code>Book</code> "ничего не знает" про модель <code>BookInstance</code>), то она не имеет никакой возможности (по умолчанию) для получения множества соответствующих записей. Для того, чтобы обойти эту проблему, Django конструирует соответствующую функцию "обратного просмотра" ("reverse lookup"), которой вы можете воспользоваться. Имя данной функции создается в нижнем регистре и состоит из имени модели, в которой был объявлен <code>ForeignKey</code> (то есть, <code>bookinstance</code>), за которым следует <code>_set</code> (то есть функция, созданная для <code>Book</code> будет иметь вид <code>bookinstance_set()</code>).</p> + +<div class="note"> +<p><strong>Примечание</strong>: Здесь мы используем <code>all()</code> для получения всех записей (по умолчанию). Вы, наверное, могли бы использовать метод <code>filter()</code> для получения подмножетсва записей в коде, но, к сожалению, вы НЕ можете применить данный вызов в шаблоне, потому что вы не можете передать в нем (в шаблоне) аргументы в функцию.</p> + +<p>Обратите внимание, что если вы не определяете порядок выдачи данных (в вашем отображении, или в модели), то сервер разработки "выкинет" сообщения об ошибках, похожие на следующие:</p> + +<pre>[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637 +/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]> + allow_empty_first_page=allow_empty_first_page, **kwargs) +</pre> + +<p>Это случилось потому что, <a href="https://docs.djangoproject.com/en/1.10/topics/pagination/#paginator-objects">paginator object</a> (далее объект постраничного вывода) ожидает видеть некую упорядоченность ORDER BY при запросе к базе данных. Без этого, он не сможет гарантировать правильный вывод полученных данных!<strong> </strong></p> + +<p>Данное руководство пока не дошло до описания <strong>Pagination</strong> (пока, но скоро будет), и поскольку вы не можете использовать функцию <code>sort_by()</code> и передавать параметр (по той же причине, что и <code>filter()</code>) вы должны выбрать один из трех вариантов дальнейших действий:</p> + +<ol> + <li>Добавить атрибут <code>ordering</code> внутри <code>Meta-класса</code> объявленного в вашей модели.</li> + <li>Добавить атрибут <code>queryset</code> в вашей реализации класса отображения, определяющего <code>order_by()</code>.</li> + <li>Добавить метод <code>get_queryset</code> в вашу реализацию класса отображения и также определить метод <code>order_by()</code>.</li> +</ol> + +<p>Если вы выбрали пункт номер один с <code>Meta-классом</code> для модели Author (вероятно, не такой гибкий как вариант с настройкой класса отображения, но тем не менее, достаточно простой), вы должны прийти к чему-то похожему на следующее:</p> + +<pre>class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return '%s, %s' % (self.last_name, self.first_name) + +<strong> class Meta: + ordering = ['last_name']</strong></pre> + +<p>Конечно же, поле не обязательно должно иметь имя <code>last_name</code>: оно может быть любым.</p> + +<p>И последнее, но не окончательное, вы должны сортировать по атрибуту/колонке, которая была проиндексирована (уникально, или нет) в вашей базе данных для того, чтобы избежать проблем с быстродействием. Конечно, это не является необходимым в данном примере (и мы, вероятно, забегаем далеко вперед), если у нас такое небольшое количество книг (и пользователей!), но это необходимо помнить для будущих проектов.</p> +</div> + +<h2 id="Как_это_теперь_выглядит">Как это теперь выглядит?</h2> + +<p>На данный момент мы должны были создать все необходимое для показа страниц со списком книг и детальной информацией. Запустите сервер (<code>python3 manage.py runserver</code>) и откройте ваш браузер <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>.</p> + +<div class="warning"> +<p><strong>Предупреждение:</strong> Не кликайте на каком-либо авторе, - ссылки пока не заданы — это будет вашим дополнительным заданием!</p> +</div> + +<p>Кликните ссылку <strong>All books</strong> для перехода на страницу со списком книг. </p> + +<p><img alt="Book List Page" src="https://mdn.mozillademos.org/files/14049/book_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 823px;"></p> + +<p>Затем кликните на ссылку одной из ваших книг. Если все настроено как надо, то вы должны увидеть то, что указано на картинке.</p> + +<p><img alt="Book Detail Page" src="https://mdn.mozillademos.org/files/14051/book_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 783px; margin: 0px auto; width: 926px;"></p> + +<h2 id="Постраничный_вывод_(Pagination)">Постраничный вывод (Pagination)</h2> + +<p>Если у вас всего лишь несколько записей в базе данных, то наша страница вывода списка книг будет выглядеть отлично. Тем не менее, когда у вас появятся десятки, или сотни записей ваша страница станет значительно дольше загружаться (и станет слишком длинной для комфортного просмотра). Решением данной проблемы является добавление постраничного вывода (Pagination) к вашему отображению списка, который будет выводить ограниченное количество элементов на каждой странице.</p> + +<p>Django имеет отличный встроенный механизм для постраничного вывода. Даже более того, он встроен в обобщенный класс отображения списков, следовательно вам не нужно проделывать большой объем работы, чтобы воспользоваться возможностями постраничного вывода!</p> + +<h3 id="Отображения">Отображения</h3> + +<p>Откройте <strong>catalog/views.py</strong> и добавьте поле <code>paginate_by</code> как показано жирным в следующем фрагменте.</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + <strong>paginate_by = 10</strong></pre> + +<p>Как только у вас появится более 10 записей в базе данных отображение начнет формировать постраничный вывод данных, которые он передает шаблону. К различным страницам данного вывода можно получить доступ при помощи параметров GET-запроса — к странице 2 вы можете получить доступ, используя URL-адрес: <code>/catalog/books/<strong>?page=2</strong></code>.</p> + +<h3 id="Шаблоны">Шаблоны</h3> + +<p>Теперь, когда данные выводятся постранично, нам надо добавить функционал переключения между страницами в шаблона страницы. Поскольку мы хотели бы использовать данный механизм для всех списков на сайте, то мы пропишем его в базовом шаблоне сайта.</p> + +<p>Откройте <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> и, ниже блока <code>content</code>, вставьте блок (во фрагменте не выделен жирным), отвечающий за постраничный вывод. Данный код, в первую очередь, проверяет "включен" ли механизм постраничного вывода для данной страницы и если это так, то он добавляет ссылки <code>next</code> и <code>previous,</code>соответственно (а также, номер текущей страницы). </p> + +<pre class="brush: python"><strong>{% block content %}{% endblock %}</strong> + +{% block pagination %} + {% if is_paginated %} + <div class="pagination"> + <span class="page-links"> + {% if page_obj.has_previous %} + <a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}">previous</a> + {% endif %} + <span class="page-current"> + Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}. + </span> + {% if page_obj.has_next %} + <a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}">next</a> + {% endif %} + </span> + </div> + {% endif %} +{% endblock %} </pre> + +<p>Параметр <code>page_obj</code> является объектом типа <a href="https://docs.djangoproject.com/en/1.10/topics/pagination/#paginator-objects">Paginator</a>, который будет создаваться каждый раз, когда будет применяться постраничный вывод данных для текущей страницы. Он позволяет получить всю информацию о текущей странице, о предыдущих страницах, сколько всего страниц и так далее. </p> + +<p>Мы используем <code>\{{ request.path }}</code> для получения URL-адреса текущей страницы, для того, чтобы создать ссылки на соответствующие страницы, обратите внимание, что данный вызов не зависит от объекта <code>page_obj</code>и, таким образом, может использоваться отдельно.</p> + +<p>На этом все!</p> + +<h3 id="Как_это_выглядит">Как это выглядит?</h3> + +<p>Картинка ниже показывает как выглядит постраничный вывод — если вы не добавили более 10 записей в вашу базу данных, тогда вы можете проверить как это работает, просто уменьшив значение в <code>paginate_by,</code> в файле <strong>catalog/views.py</strong>. Для получения результата, соответствующего картинке ниже, мы изменили<code>paginate_by = 2</code>.</p> + +<p>Ссылки на страницы показаны в нижней части страницы. Показаны ссылки следующая/предыдущая в зависимости от того на какой странице вы в данный момент находитесь.</p> + +<p><img alt="Book List Page - paginated" src="https://mdn.mozillademos.org/files/14057/book_list_paginated.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 924px;"></p> + +<h2 id="Проверьте_себя">Проверьте себя</h2> + +<p>Дополнительным задание в данной статье и для завершения данного этапа проекта будет создание отображений детальной информации об авторе и их списка. Эти отображения должны находиться по следующим адресам:</p> + +<ul> + <li><code>catalog/authors/</code> — Список авторов.</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— Детальная информация об авторе со значением первичного ключа равным <em><code><id></code></em></li> +</ul> + +<p>Соответствующий код для URL-преобразований и оторажений должен быть идентичным коду для списка книг и детальной информаци о книге <code>Book</code>, который мы создали ранее. Шаблоны будут отличаться, но будут иметь похожее поведение.</p> + +<div class="note"> +<p><strong>Примечание</strong>:</p> + +<ul> + <li>Когда вы создадите URL-преобразование для страницы списка авторов вам понадобится обновить ссылку <strong>All authors</strong> в базовом шаблоне. Следуйте <a href="#Update_the_base_template">тем же путем</a>, который мы проделали когда обновляли ссылку <strong>All books</strong>.</li> + <li>Когда вы создадите URL-преобразование для страницы с детальной информацией об авторе, вы должны будете обновить <a href="#Creating_the_Detail_View_template">шаблон детальной информации о книге</a> (<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>), таким образом, чтобы ссылка автора указывала на страницу с детальной информации о нем (а не быть пустой). Данная ссылка будет иметь вид как указано жирным во фрагменте ниже. + <pre class="brush: html"><p><strong>Author:</strong> <a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a></p> +</pre> + </li> +</ul> +</div> + +<p>Когда вы закончите, ваши страницы должны будут выглядеть как на картинке.</p> + +<p><img alt="Author List Page" src="https://mdn.mozillademos.org/files/14053/author_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> +</ul> + +<p><img alt="Author Detail Page" src="https://mdn.mozillademos.org/files/14055/author_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 358px; margin: 0px auto; width: 825px;"></p> + +<ul> +</ul> + +<h2 id="Итоги">Итоги</h2> + +<p>Поздравляем! Наш базовый функционал библиотеки готов! </p> + +<p>В данной статье мы изучили как применять обобщенные классы отображения списка и детальной информации, и использовать их для создания страниц отображения наших книг и авторов. Кроме того, мы многое узнали о паттернах преобразования, построенных на основе регулярных выражений, а также то, как вы можете передавать данные из URL-адреса в ваше отображение. Мы изучили несколько приемов применения шаблонов. В самом конце мы показали как осуществлять постраничный вывод списков, так, что наши списки управляются даже тогда, когда они содерждат много записей.</p> + +<p>В нашей следующей статье мы расширим нашу библиотеку, путем поддержки пользовательких аккаутов, и так образом продемонстрируем аутетификацию, разграничение уровней доступа, сессии и формы.</p> + +<h2 id="Дополнительная_информация">Дополнительная информация</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/">Встроенные обобщенные классы отображения</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-display/">Обобщенный вид отображения</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/intro/">Введение в отображения на основе классов</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins">Встроенные теги шаблона и фильтры</a> (Django docs).</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/pagination/">Постраничный вывод (Pagination)</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</p> |