1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
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>
|