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
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
|
---
title: 'Tutorial de Django Parte 6: Listas genéricas y vistas de detalle'
slug: Learn/Server-side/Django/Generic_views
tags:
- Aprender
- Principiante
- Tutorial
- django
- plantillas django
- vistas django
translation_of: Learn/Server-side/Django/Generic_views
---
<div>{{LearnSidebar}}</div>
<div>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</div>
<p class="summary">Este tutorial extiende nuestro sitio web de la <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style="font-size: 1.25rem;">BibliotecaLocal</a><span style="font-size: 1.25rem;">, añadiendo páginas de listas y detalles de libros y autores. Aquí aprenderemos sobre vistas genéricas basadas en clases, y mostraremos cómo éstas pueden reducir la cantidad de código que tienes que escribir para casos de uso común. También entraremos en el manejo de URL en gran detalle, mostrando cómo realizar un emparejamiento de patrones básico</span><span style="font-size: 1.25rem;">.</span></p>
<table class="learn-box standard-table">
<tbody>
<tr>
<th scope="row">Requisitos previos:</th>
<td>
<p>Completa todos los tópicos anteriores del tutorial, incluyendo <a href="https://developer.mozilla.org/es/docs/Learn/Server-side/Django/Home_page">Tutorial de Django Parte 5: Creación de tu página de inicio</a>.</p>
</td>
</tr>
<tr>
<th scope="row">Objetivo:</th>
<td>
<p>Entender dónde y cómo usar las vistas genéricas basadas en clases, y cómo extraer patrones desde las URLs y enviar la información a las vistas.</p>
</td>
</tr>
</tbody>
</table>
<h2 id="Visión_General">Visión General</h2>
<p>En este tutorial vamos a completar la primera versión del sitio web <a href="https://developer.mozilla.org/es/docs/Learn/Server-side/Django/Tutorial_local_library_website">BibliotecaLocal</a> añadiendo páginas de lista y detalle para libros y autores (o para ser más precisos, te mostraremos cómo implementar las páginas de libros, ¡y te dejaremos crear las páginas de autores por tí mismo!)</p>
<p>El proceso es similar al de creación de la página índice, que mostramos en el tutorial anterior. Aquí también necesitaremos crear mapeos URL, vistas y plantillas. La principal diferencia es que para las páginas de detalle tendremos el reto adicional de extraer información desde patrones en las URLs y pasarla a la vista. Para estas páginas vamos a mostrar un tipo de vista completamente diferente: vistas genéricas de lista y detalle basadas en clases. Estas pueden reducir significativamente la cantidad de código requerido para las vistas, haciéndolas más fáciles de escribir y mantener.</p>
<p>La parte final del tutorial mostrará cómo paginar tu información al usar vistas de lista genéricas basadas en clases.</p>
<h2 id="Página_de_lista_de_libros">Página de lista de libros</h2>
<p>La página de lista de libros desplegará una lista con todos los registros de libros disponibles en la página, a la cual se accede usando la URL: <code>catalog/books/</code>. La página desplegará un título y un autor para cada registro, siendo el título un hipervículo a la página de detalle de libro relacionada. La página tendrá la misma estructura y navegación que todas las demás páginas en el sitio, y por tanto podemos extender la plantilla base (<strong>base_generic.html</strong>) que creamos en el tutorial anterior.</p>
<h3 id="Mapeo_URL">Mapeo URL</h3>
<p>Abre <strong>/catalog/urls.py</strong> y copia allí la línea que se muestra abajo en negrita. De manera muy similar al mapeo de nuestro índice, esta función <code>url()</code> define una expresión regular (RE) para comparala con la URL (<strong>r'^books/$'</strong>), una función de vista que será llamada si la URL coincide (<code>views.BookListView.as_view()</code>) y un nombre para este mapeo en particular.</p>
<pre class="brush: python">urlpatterns = [
url(r'^$', views.index, name='index'),
<strong> url(r'^books/$', views.BookListView.as_view(), name='books'),</strong>
]</pre>
<p>Esta expresión regular coincide con URLs iguales a <code>books/</code> (<code>^</code> es un marcador de inicio de cadena y <code>$</code> es un marcador de fin de cadena). Como se discutió en el tutorial anterior, la URL debió previamente haber coincidido con <code>/catalog</code>, de modo que la vista será llamada en realidad para la URL: <code>/catalog/books/</code>.</p>
<p>La función de vista tiene un formato diferente al anterior -- eso es porque esta vista será en realidad implementada como una clase. Heredaremos desde una función de vista genérica existente que ya hace la mayoría de lo que queremos que esta función de vista haga, en lugar de escribir la nuestra propia desde el inicio.</p>
<p>Para las vistas basadas en clases de Django, accedemos a una función de vista apropiada llamando al método de clase <code>as_view()</code>. Esto hace todo el trabajo de crear una instancia de la clase, y asegurarse de que los métodos controladores correctos sean llamados para las solicitudes HTTP entrantes.</p>
<h3 id="Vista_(basada_en_clases)">Vista (basada en clases)</h3>
<p>Podríamos fácilmente escribir la vista de lista de libros como una función regular (tal como nuestra vista de índice anterior), la cual consultaría a la base de datos por todos los libros, y luego llamar a <code>render()</code> para pasar dicha lista a una plantilla específica. Sin embargo, en lugar de eso usaremos una vista de lista genérica basada en clases (ListView) -- una clase que hereda desde una vista existente. Debido a que la vista genérica ya implementa la mayoría de la funcionalidad que necesitamos, y sigue la práctica adecuada de Django, seremos capaces de crear una vista de lista más robusta con menos código, menos repetición, y por último menos mantenimiento.</p>
<p>Abre <strong>catalog/views.py</strong>, y copia el siguiente código al final del archivo:</p>
<pre class="brush: python">from django.views import generic
class BookListView(generic.ListView):
model = Book</pre>
<p>¡Eso es todo! La vista genérica consultará a la base de datos para obtener todos los registros del modelo especificado (<code>Book</code>) y renderizará una plantilla ubicada en <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> (que crearemos más abajo). Dentro de la plantilla puedes acceder a la lista de libros mediante la variable de plantilla llamada <code>object_list</code> O <code>book_list</code> (esto es, genéricamente, "<code><em>nombre_del_modelo</em>_list</code>").</p>
<div class="note">
<p><strong>Nota</strong>: Esta ruta complicada para la ubicación de la plantilla no es un error de digitación -- las vistas genéricas buscan plantillas en <code><em>/application_name/the_model_name</em>_list.html</code> (<code>catalog/book_list.html</code> en este caso) dentro del directorio de la aplicación <code>/<em>application_name</em>/templates/</code> (<code>/catalog/templates/</code>).</p>
</div>
<p>Puedes añadir atributos para cambiar el comportamiento por defecto de arriba. Por ejemplo, puedes especificar otro archivo de plantilla si necesitas tener múltiples vistas que usen el mismo modelo, o puedes querer usar un nombre diferente de variable de plantilla si <code>book_list</code> no resulta intuitivo para tu caso particular de uso de plantilla. Posiblemente la variación más útil es cambiar/filtrar el conjunto de resultados que se devuelve, así, en lugar de listar todos los libros podrías listar los 5 libros más leídos por otros usuarios.</p>
<pre class="brush: python">class BookListView(generic.ListView):
model = Book
context_object_name = 'my_book_list' # your own name for the list as a template variable
queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location</pre>
<h4 id="Sobreescribiendo_métodos_en_vistas_basadas_en_clases">Sobreescribiendo métodos en vistas basadas en clases</h4>
<p>Si bien no necesitamos hacerlo aquí, puedes también sobreescribir algunos de los métodos de clase.</p>
<p>Por ejemplo, podemos sobreescribir el método <code>get_queryset()</code> para cambiar la lista de registros devueltos. Esto es más flexible que simplemente ajustar el atributo <code>queryset</code> como lo hicimos en el fragmento de código anterior (aunque en este caso no existe un beneficio real):</p>
<p> </p>
<pre class="brush: python">class BookListView(generic.ListView):
model = Book
def get_queryset(self):
return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
</pre>
<p> </p>
<p>Podríamos también sobreescribir <code>get_context_data()</code> con el objeto de pasar variables de contexto adicionales a la plantilla (ej. la lista de libros se pasa por defecto). El fragmento de abajo muestra cómo añadir una variable llamada "some_data" al contexto (la misma estaría entonces disponible como una variable de plantilla).</p>
<pre class="brush: python">class BookListView(generic.ListView):
model = Book
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(BookListView, self).get_context_data(**kwargs)
# Get the blog from id and add it to the context
context['some_data'] = 'This is just some data'
return context</pre>
<p>Cuando se hace esto es importante seguir este patrón:</p>
<ul>
<li>Primero obtener el contexto existente desde nuestra superclase.</li>
<li>Luego añadir tu nueva información de contexto.</li>
<li>Luego devolver el nuevo contexto (actualizado).</li>
</ul>
<div class="note">
<p><strong>Nota</strong>: Revisa <a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs) para muchos más ejemplos de lo que puedes hacer.</p>
</div>
<h3 id="Creando_la_plantilla_de_Vista_de_Lista">Creando la plantilla de Vista de Lista</h3>
<p>Crea el archivo HTML <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> y copia allí el texto de abajo. Como se discutió antes, este es el archivo de plantilla por defecto esperado por la vista de lista genérica basada en clases (para un modelo llamado <code>Book</code> en una aplicación llamada <code>catalog</code>).</p>
<p>Las plantillas para las vistas genéricas son como cualquier otra plantilla (si bien el contexto/información enviada a la plantilla puede variar, por supuesto). Como con nuestra plantilla <em>índice</em>, extendemos nuestra plantilla base en la primera línea, y luego reemplazamos el bloque llamado <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>La vista envía el contexto (lista de libros) por defecto como <code>object_list</code> y <code>book_list</code> (son áliases, cualquiera de ellos funcionará).</p>
<h4 id="Ejecución_condicional">Ejecución condicional</h4>
<p>Usamos las etiquetas de plantilla <code><a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#if">if</a></code>, <code>else</code> y <code>endif</code> para revisar si la <code>book_list</code> ha sido definida y no está vacía. Si <code>book_list</code> está vacía, entonces la cláusula <code>else</code> despliega un texto que explica que no existen libros a listar. Si <code>Book_list</code> no está vacía, entonces iteramos a través de la lista de libros.</p>
<pre class="brush: html"><strong>{% if book_list %}</strong>
<!-- code here to list the books -->
<strong>{% else %}</strong>
<p>There are no books in the library.</p>
<strong>{% endif %}</strong>
</pre>
<p>La condicional de arriba solo revisa un caso, pero puedes revisar condiciones adicionales usando la etiqueta de plantilla <code>elif</code> (ej. <code>{% elif var2 %}</code>). Para mayor información sobre operadores condicionales mira: <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>, y <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#ifchanged">ifchanged</a> en <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins">Built-in template tags and filters</a> (Django Docs).</p>
<h4 id="Lazos_For">Lazos For</h4>
<p>La plantilla usa las etiquetas de plantilla <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#for">for</a> y <code>endfor</code> para iterar a través de la lista de libros, como se muestra abajo. Cada iteración asigna a la variable de plantilla <code>book</code> la información para el ítem actual de la lista.</p>
<pre class="brush: html">{% for <strong>book</strong> in book_list %}
<li> <!-- code here get information from each <strong>book</strong> item --> </li>
{% endfor %}
</pre>
<p>Si bien no se usan aquí, Django creará otras variables dentro del lazo que puedes usar para monitorear la iteración. Por ejemplo, puedes revisar la variable <code>forloop.last</code> para realizar un procesamiento condicional la última vez que el lazo se ejecuta.</p>
<h4 id="Accediendo_a_las_variables">Accediendo a las variables</h4>
<p>El código dentro del lazo crea un ítem de lista para cada libro, que muestra tanto el título (como un enlace hacia la vista de detalle que aún no creamos) como el autor.</p>
<pre class="brush: html"><a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}})
</pre>
<p>Accedemos a los <em>campos</em> del registro del libro asociado usando la "notación de punto" (ej. <code>book.title</code> y <code>book.author</code>), donde el texto que sigue a la palabra <code>book</code> es el nombre del campo (como se definió en el modelo).</p>
<p>También podemos invocar <em>funciones</em> en el modelo desde dentro de nuestra plantilla -- en este caso invocamos a <code>book.get_absolute_url()</code> para obtener una URL que se podría usar para desplegar la página de detalle relacionada. Esto funciona siempre y cuando la función no tenga ningún argumento (¡no hay forma de enviar argumentos!).</p>
<div class="note">
<p><strong>Nota</strong>: Debemos tener cuidado de los "efectos secundarios" al invocar funciones en las plantillas. Aquí solo obtenemos una URL para desplegar, pero una función puede hacer casi cualquier cosa -- ¡no quisieramos borrar nuestra base de datos (por ejemplo) solo por renderizar nuestra plantilla!</p>
</div>
<h4 id="Actualizar_la_plantilla_base">Actualizar la plantilla base</h4>
<p>Abre la plantilla base (<strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>) e inserta <strong>{% url 'books' %}</strong> en el enlace URL para <strong>All books</strong>, como se muestra abajo. Esto habilitará el enlace en todas las páginas (podemos ubicar esto exitosamente en su lugar ahora que hemos creado el mapeo url "books").</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="¿Cómo_se_ve">¿Cómo se ve?</h3>
<p>Aún no podrás ver la lista de libros, porque aún nos falta una dependencia -- el mapeo URL para las páginas de detalle de libros, que se necesita para crear los hipervínculos a los libros individuales. Mostraremos tanto la lista de libros como las vistas de detalle después de la siguiente sección.</p>
<h2 id="Página_de_detalle_de_libros">Página de detalle de libros</h2>
<p>La página de detalle de libro desplegará información sobre un libro específico, a la que se accede usando la URL <code>catalog/book/<em><id></em></code> (donde <code><id></code> es la clave primaria para el libro). Además de los campos en el modelo <code>Book</code> (autor, resumen, ISBN, idioma, y género), listaremos también los detalles de las copias disponibles (<code>BookInstances</code>) incluyendo su estado, fecha de devolución esperada, edición e id. Esto permitirá a nuestros lectores no solo saber sobre el libro en sí, sino también confirmar si está disponible o cuándo lo estará.</p>
<h3 id="Mapeo_URL_2">Mapeo URL</h3>
<p>Abre <strong>/catalos/urls.py</strong> y añade el mapeador URL <strong>'book-detail'</strong> que se muestra abajo en negrita. Esta función <code>url()</code> define un patrón, vista de detalle genérica basada en clases asociada, y un nombre.</p>
<pre class="brush: python">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>A diferencia de nuestros mapeadores anteriores, en este caso estamos usando nuestra expresión regular (RE) para comparar frente a un "patrón" real en lugar de una simple cadena. Lo que hace esta RE en particular es coincidir con cualquier URL que empiece por <code>book/</code>, seguido de uno o más <em>dígitos</em> (números) antes del marcador de fin de línea. Mientras se realiza la comparación, se "capturan" los dígitos y son enviados a la función de vista como un parámetro llamado <code>pk</code>.</p>
<div class="note">
<p><strong>Nota</strong>: Como ya se discutió antes, nuestra URL coincidente es en realidad <code>catalog/book/<digits></code> (como estamos en la aplicación <strong>catalog</strong>, se asume <code>/catalog/</code>).</p>
</div>
<div class="warning">
<p><strong>Importante</strong>: La vista de detalle genérica basada en clases <em>espera</em> que se le envíe un parámetro llamado pk. Si estás escribiendo tu propia vista de función, puedes usar el nombre de parámetro que quieras, o incluso enviar la información como un argumento sin nombre.</p>
</div>
<h4 id="Una_breve_introducción_a_las_expresiones_regulares">Una breve introducción a las expresiones regulares</h4>
<p>Las <a href="https://docs.python.org/3/library/re.html">expresiones regulares</a> son una herramienta de mapeo de patrones increíblemente poderosa. Hemos dicho poco sobre ellas hasta ahora porque estuvimos comparando con cadenas fijas en nuestras URLs (en lugar de con patrones) y porque son, francamente, bastante intuitivas e intimidantes para los principiantes.</p>
<div class="note">
<p><strong>Nota</strong>: ¡No te asustes! Los tipos de patrones con los que estaremos comparando son bastante simples, ¡y en muchos casos están bien documentados!</p>
</div>
<p>Lo primero que hay que saber es que las expresiones regulares deberían ser declaradas normalmente usando la sintaxis de literal de cadena sin procesar (esto es, están encerradas así: <strong>r'<tu expresión regular va aquí>'</strong>).</p>
<p>Las partes principales de la sintaxis que necesitarás saber para declarar las coincidencias de patrones son:</p>
<table class="standard-table">
<thead>
<tr>
<th scope="col">Símbolo</th>
<th scope="col">Significado</th>
</tr>
</thead>
<tbody>
<tr>
<td>^</td>
<td>Coincide con el inicio del texto</td>
</tr>
<tr>
<td>$</td>
<td>Coincide con el fin del texto</td>
</tr>
<tr>
<td>\d</td>
<td>Coincide con un dígito (0, 1, 2, ... 9)</td>
</tr>
<tr>
<td>\w</td>
<td>
<p>Concide con un caracter de palabra, ej. cualquier caracter del alfabeto en mayúscula o minúscula, dígito o guión bajo (_)</p>
</td>
</tr>
<tr>
<td>+</td>
<td>
<p>Concide con uno o más caracteres del precedente. Por ejemplo, para coincidir con uno o más dígitos usarías <code>\d+</code>. Para concidir con uno o más caracteres "a", podrías usar <code>a+</code></p>
</td>
</tr>
<tr>
<td>*</td>
<td>
<p>Concide con cero o más caracteres del precedente. Por ejemplo, para coincidir con nada o una palabra podrías usar <code>\w*</code></p>
</td>
</tr>
<tr>
<td>( )</td>
<td>
<p>Captura la parte del patrón dentro de los paréntesis. Todos los valores capturados serán enviados a la vista como parámetros sin nombre (si se captura múltiples patrones, los parámetros asociados serán enviados en el órden en que fueron declaradas las capturas).</p>
</td>
</tr>
<tr>
<td>(?P<<em>name</em>>...)</td>
<td>
<p>Captura el patrón (indicado por ...) como una variable con nombre (en este caso "name"). Los valores capturados se envían a la vista con el nombre especificado. Tu vista debe, por tanto, ¡declarar un argumento con el mismo nombre!</p>
</td>
</tr>
<tr>
<td>[ ]</td>
<td>
<p>Concide con un caracter del conjunto. Por ejemplo, [abc] coincidirá con 'a' o 'b' o 'c'. [-\w] coincidrá con el caracter '-' o con cualquier letra.</p>
</td>
</tr>
</tbody>
</table>
<p>¡La mayoría de los caracteres restantes se pueden tomar literalmente!</p>
<p>Consideremos algunos ejemplos reales de patrones:</p>
<table class="standard-table">
<thead>
<tr>
<th scope="col">Patrón</th>
<th scope="col">Descripción</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>r'^book/(?P<pk>\d+)$'</strong></td>
<td>
<p>Esta es la RE usada en nuestro mapeador url. Concide con una cadena que tiene <code>book/</code> al inicio de la línea (<strong>^book/</strong>), luego tiene uno o más dígitos (<code>\d+</code>), y luego termina (sin ningún caracter que no sea un dígito antes del marcador de fin de línea).</p>
<p>También captura todos los dígitos <strong>(?P<pk>\d+)</strong> y los envía a la vista como un parámetro llamado 'pk'. <strong>¡Los valores capturados siempre se envían como cadena!</strong></p>
<p>Por ejemplo, esto coincidiría con <code>book/1234</code>, y enviaría una variable <code>pk='1234'</code> a la vista.</p>
</td>
</tr>
<tr>
<td><strong>r'^book/(\d+)$'</strong></td>
<td>
<p>Esta expresión coincide con las mismas URLs que el caso anterior. La información capturada se enviaría a la vista como un argumento sin nombre.</p>
</td>
</tr>
<tr>
<td><strong>r'^book/(?P<stub>[-\w]+)$'</strong></td>
<td>
<p>Esta expresión coincide con una cadena que tiene <code>book/</code> al inicio de la línea (<strong>^book/</strong>), luego tiene uno o más caracteres que son <em>o bien</em> '-' o una letra (<strong>[-\w]+</strong>), y luego termina. También captura este conjunto de caracteres y los envía a la vista como un parámetro llamado 'stub'.</p>
<p>Este es un patrón bastante típico para un "talón". Estos talones representan claves primarias en URLs amigables basadas en palabras para la información. Podrías usar un talón si quisieras que la URL de un libro sea más informativa. Por ejemplo, <code>/catalog/book/the-secret-garden</code> en lugar de <code>/catalog/book/33</code>.</p>
</td>
</tr>
</tbody>
</table>
<p>Puedes capturar múltiples patrones en una sola comparación, y por tanto codificar bastantes datos diferentes en una URL.</p>
<div class="note">
<p><strong>Nota</strong>: Como reto, considera cómo podrías codificar una url para listar todos los libros publicados en un año, mes y día en particular, y la RE que podría usarse para la comparación.</p>
</div>
<h4 id="Enviado_opciones_adicionales_en_tus_mapeos_URL">Enviado opciones adicionales en tus mapeos URL</h4>
<p>Una característica que no hemos usado aquí, pero que te puede resultar valiosa, es que puedes declarar y enviar <a href="https://docs.djangoproject.com/en/1.10/topics/http/urls/#views-extra-options">opciones adicionales</a> a la vista. Las opciones se declaran como un diccionario que envías como el tercer argumento sin nombre a la función <code>url()</code>. Esta estrategia puede resultar útil si quiere usar la misma vista para múltiples recursos, y enviar información para configurar su comportamiento en cada caso (abajo suministramos una plantilla diferente en cada caso).</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>Nota</strong>: Tanto las opciones extra como los patrones capturados con nombre se envían a la vista como argumentos <em>con nombre</em>. Si usas el <strong>mismo nombre</strong> tanto para un patrón capturado como para una opción extra, solo el valor del patrón capturado será enviado a la vista (el valor especificado para la opción extra será eliminado).</p>
</div>
<h3 id="Vista_(basada_en_clases)_2">Vista (basada en clases)</h3>
<p>Abre <strong>catalog/views.py</strong> y copia el siguiente código al final del archivo:</p>
<pre class="brush: python">class BookDetailView(generic.DetailView):
model = Book</pre>
<p>¡Eso es todo! Lo único que necesitas hacer ahora es crear una plantilla llamada <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>, y la vista enviará la información en la base de datos para el registro del libro específico, extraído por el mapeador URL. Dentro de la plantilla puedes acceder a la lista de libros mediante la variable de plantilla llamada <code>object</code> o <code>book</code> (esto es, genéricamente, "<em><code>el_nombre_del_modelo</code></em>").</p>
<p>Si lo necesitas puedes cambiar la plantilla usada y el nombre del objeto de contexto usado para referenciar al libro en la plantilla. Puedes también sobreescribir métodos para, por ejemplo, añadir información adicional al contexto.</p>
<h4 id="¿Qué_sucede_si_el_registro_no_existe">¿Qué sucede si el registro no existe?</h4>
<p>Si un registro solicitado no existe, la vista de detalle genérica basada en clases lanzará automáticamente por tí una excepción de tipo Http404 -- en producción, esto desplegará automáticamente una página apropiada de "recurso no encontrado", que puedes personalizar si lo deseas.</p>
<p>Solo para darte una idea sobre cómo funciona esto, el fragmento de código de abajo demuestra cómo implementarías la vista basada en clases como una función, si <strong>no</strong> estuvieras usando la vista de detalle genérica basada en clases.</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>Primero, la vista intenta recuperar el registro del libro específico desde el modelo. Si esto falla, la vista debería lanzar una excepción de tipo <code>Http404</code> para indicar que el libro "no se encuentra". El último paso es, como de costumbre, llamar a <code>render()</code> con el nombre de la plantilla y la información del libro en el parámetro <code>context</code> (como un diccionario).</p>
<div class="note">
<p><strong>Nota</strong>: <code>get_object_or_404()</code> (que se muestra comentado arriba), es un atajo conveniente para lanzar una excepción de tipo <code>Http404</code> si el registro no se encuentra.</p>
</div>
<h3 id="Creando_la_plantilla_de_Vista_de_Detalle">Creando la plantilla de Vista de Detalle</h3>
<p>Crea el archivo HTML <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong> y ponle el contenido de abajo. Como se discutió antes, este es el nombre de archivo por defecto esperado por la vista de detalle genérica basada en clases (para un modelo llamado <code>Book</code> en una aplicación llamada <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 == 'm' %}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>El enlace <code>author</code> en la plantilla de arriba tiene una URL vacía porque no hemos creado aún una página de detalle de autor. Una vez que esta exista, deberías actualizar la URL así:</p>
<pre><a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a>
</pre>
</div>
<p>Aunque es un poco más larga, casi todo lo que existe en esta plantilla se ha descrito previamente:</p>
<ul>
<li>Extendemos nuestra plantilla base y sobreescribimos el bloque "content"</li>
<li>Usamos procesamiento condicional para determinar si desplegar o no contenidos específicos</li>
<li>Usamos lazos <code>for</code> para iterar a través de listas de objetos.</li>
<li>Accedemos a los campos de contexto usando la notación de puntos (como hemos usado la vista de detalle genérica, el contexto se llama <code>book</code>; podríamos también usar "<code>object</code>")</li>
</ul>
<p>Lo interesante que no hemos visto previamente es la función <code>book.bookinstance_set.all()</code>. Este método es "automágicamente" creado por Django para devolver el conjunto de registros de <code>BookInstance</code> asociado con un <code>Book</code> en particular.</p>
<pre class="brush: python">{% for copy in book.bookinstance_set.all %}
<!-- code to iterate across each copy/instance of a book -->
{% endfor %}</pre>
<p>Este método es necesario porque has declarado un campo <code>ForeignKey</code> (uno-a-muchos) únicamente en la lado "uno" de la relación. Como no haces nada para declarar la relación en el otro modelo ("muchos"), este no tiene ningún campo para obtener el conjunto de registros asociados. Para superar este problema, Django construye una función apropiadamente llamada "búsqueda reversa" que puedes usar. El nombre de la función se construye convirtiendo a minúsculas el nombre del modelo donde la <code>ForeignKey</code> fue declarada, seguido por <code>_set</code> (así, la función creada en <code>Book</code> es <code>bookinstance_set()</code>).</p>
<div class="note">
<p><strong>Nota</strong>: Aquí usamos <code>all()</code> para obtener todos los registros (la opción por defecto). A pesar de que puedes usar el método <code>filter()</code> para obtener un subconjunto de registros en el código, no puedes hacerlo directamente en las plantillas porque no puedes especificar argumentos para las funciones.</p>
<p>Ten también cuidado de que si no defines un orden (en tu vista o modelo basado en clases), verás errores arrojados por el servidor de dearrollo como este:</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>Eso sucede porque el <a href="https://docs.djangoproject.com/en/1.10/topics/pagination/#paginator-objects">objeto paginador</a> espera ver una cláusula ORDER BY siendo ejecutada en tu base de datos subyacente. Sin ella, ¡no puede estar seguro de que los registros devueltos están en el orden correcto!</p>
<p>Este tutorial no llegó a la <strong>Paginación</strong> (aún, pero pronto lo hará), pero como no puedes uar <code>sort_by()</code> y enviar un parámetro (el mismo con <code>filter()</code> descrito arriba) tendrás que escoger entre tres opciones:</p>
<ol>
<li>Añadir un <code>ordering</code> dentro de una declaración <code>class Meta</code> en tu modelo.</li>
<li>Añadir un atributo <code>queryset</code> en tu vista basada en clases personalizada, especificando un <code>order_by()</code>.</li>
<li>Añadir un método <code>get_queryset</code> a tu vista basada en clases pesonalizada y también especificar el <code>order_by()</code>.</li>
</ol>
<p>Si te decides por la opción <code>class Meta</code> para el modelo Author (probablemente no tan flexible como personalizar la vista basada en clases, pero lo suficientemente fácil), terminarás con algo como esto:</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>Por supuesto, el campo no tiene que ser <code>last_name</code>: podría ser cualquier otro.</p>
Y por último, pero no menos importante, deberías ordenar por un atributo/columna que tenga un índice real (único o no) en tu base de datos para evitar problemas de rendimiento. Por supuesto, esto no será necesario aquí (y probablemente nos estemos adelantando mucho) para la pequeña cantidad de libros (¡y usuarios!), pero es algo a tener en cuenta para proyectos futuros.</div>
<h2 id="¿Cómo_se_ve_2">¿Cómo se ve?</h2>
<p>En este punto deberíamos haber creado todo lo necesario para desplegar tanto la lista de libros como las páginas de detalles de libros. Ejecuta el servidor (<code>python3 manage.py runserver</code>) y dirígete en tu navegador a <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>.</p>
<div class="warning">
<p><strong>Advertencia</strong>: No hagas click aún en ningún enlace de autor o de detalles de autores -- ¡los crearás en el reto!</p>
</div>
<p>Haz click en el enlace <strong>All books</strong> para desplegar la lista de libros.</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>Luego haz click en un enlace a uno de tus libros. Si todo está correcto, deberías ver algo como la siguiente pantalla.</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="Paginación">Paginación</h2>
<p>Si apenas tienes unos pocos registros, nuestra página de lista de libros se verá bien. Sin embargo, cuando llegues a las decenas o centenas de registros la página tomará progresivamente más tiempo en cargar (y tendrá demasiado contenido para navegar adecuadamente). La solución a este problema es añadir paginación a tus vistas de lista, reduciendo el número de Ítems desplegados en cada página.</p>
<p>Django incluye un excelente soporte para paginación. Mejor aún, este está incluído en las vistas de lista genéricas basadas en clases, ¡así que no tienes que hacer mucho para habilitarlo!</p>
<h3 id="Vistas">Vistas</h3>
<p>Abre <strong>catalog/views.py</strong>, y añadie la línea <code>paginate_by</code> que se muestra abajo en negrita.</p>
<pre class="brush: python">class BookListView(generic.ListView):
model = Book
<strong>paginate_by = 10</strong></pre>
<p>Con esta adición, apenas tengas más de 10 registros la vista comenzará a paginar la información que envía a la plantilla. A las diferentes páginas se accede usando parámetros GET -- para acceder a la página 2 usarías la URL: <code>/catalog/books/?<strong>page=2</strong></code>.</p>
<h3 id="Plantillas">Plantillas</h3>
<p>Ahora que la información está paginada, necesitamos añadir soporte a la plantilla para desplazarse a través del conjunto de resultados. Como podríamos querer hacer lo mismo en todas las vistas de lista, lo haremos de una forma en la que puede ser añadida a la plantilla base.</p>
<p>Abre <strong>/locallibrary/catalog/templates/<em>base_generic.html </em></strong>y copia el siguiente bloque <code>pagination</code> debajo de nuestro bloque <code>content</code> (resaltado abajo en negrita). El código primero revisa si la paginación está habilitada en la página actual. Si lo está, añade enlaces <code>next</code> y <code>previous</code> apropiados (y el número de la página actual).</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> es un objeto <a href="https://docs.djangoproject.com/en/1.10/topics/pagination/#paginator-objects">Paginator</a> que existirá si se usa la paginación en la página actual. Te permite obtener toda la información sobre la página actual, páginas anteriores, cuántas páginas hay, etc.</p>
<p>Usamos <code>\{{ request.path }}</code> para obtener la URL de la página actual para crear a su vez los enlaces de paginación. Esto es útil, porque es independiente del objeto que estamos paginando.</p>
<p>¡Eso es todo!</p>
<h3 id="¿Cómo_se_ve_3">¿Cómo se ve?</h3>
<p>La captura de pantalla de abajo muestra cómo se ve la paginación -- si no has ingresado más de 10 títulos en tu base de datos, puedes probarlo más fácilmente reduciendo el número especificado en la línea <code>paginate_by</code> en tu archivo <strong>catalog/views.py</strong>. Para obtener el resultado de abajo lo cambiamos a <code>paginate_by = 2</code>.</p>
<p>Los enlaces de paginación se muestran en la parte de abajo, con enlaces de next/previous desplegados dependiendo de en qué página estés</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="Rétate_a_tí_mismo">Rétate a tí mismo</h2>
<p>El reto en este artículo es crear las vistas de lista y detalle para autores, que se requieren para completar el proyecto. Estas deberían estar disponibles en las siguientes URLs:</p>
<ul>
<li><code>catalog/authors/</code> — La lista de todos los autores.</li>
<li><code>catalog/author/<em><id></em></code><em> </em>— La vista de detalle para el autor específico con un valor en el campo de clave primaria de <em><code><id></code></em></li>
</ul>
<p>El código requerido para los mapeadores URL y las vistas debería ser virtualmente idéntico a las vistas de lista y detalle para <code>Book</code> que creamos arriba. Las plantillas serán diferentes, pero tendrán un comportamiento similar.</p>
<div class="note">
<p><strong>Nota</strong>:</p>
<ul>
<li>Una vez que has creado el mapeador URL para la página de lista de autores, necesitarás también actualizar el enlace <strong>All authors</strong> en la plantilla base. Sigue el <a href="#Update_the_base_template">mismo proceso</a> que hicimos cuando actualizamos el enlace <strong>All books</strong>.</li>
<li>Una vez que has creado el mapeador URL para la página de detalle de autores, deberías también actualizar la <a href="#Creating_the_Detail_View_template">plantilla de vista de detalle de libros </a>(<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>) de modo que el enlace de autor apunte a tu nueva página de detalle de autor (en lugar de ser una URL vacía). La línea cambiará para añadir la etiqueta de plantilla que se muestra en negrita abajo.</li>
</ul>
<pre class="brush: html"><p><strong>Author:</strong> <a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a></p> </pre>
</div>
<p>Cuando termines, tus páginas deberían lucir similares a las capturas de pantalla de abajo.</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="Resumen">Resumen</h2>
<p>Felicitaciones, ¡la funcionalidad de nuestra biblioteca básica está ahora completa!</p>
<p>En este artículo hemos aprendido cómo usar las vistas genéricas basadas en clases de lista y detalle, y las hemos usado para crear páginas para ver nuestros libros y autores. Sobre la marcha hemos aprendido sobre coincidencia de patrones con expresiones regulares, y cómo puedes enviar información desde las URLs a tus vistas. Hemos también aprendido unos cuantos trucos más para usar plantillas. Por último hemos mostrado cómo paginar vistas de lista, de modo que nuestras listas sean manejables incluso si tenemos muchos registros.</p>
<p>En los siguientes artículos extenderemos esta biblioteca para añadir soporte para cuentas de usuario, y así demostrar la autenticación de usuarios, permisos, sesiones y formularios.</p>
<h2 id="Mira_también">Mira también</h2>
<ul>
<li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/">Vistas genéricas basadas en clases incluídas</a> (Django docs)</li>
<li><a href="https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-display/">Vistas genéricas de despliegue</a> (Django docs)</li>
<li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/intro/">Introducción a las vistas basadas en clases</a> (Django docs)</li>
<li><a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins">Etiquetas de plantilla y filtros incluídos</a> (Django docs).</li>
<li><a href="https://docs.djangoproject.com/en/1.10/topics/pagination/">Paginación</a> (Django docs)</li>
</ul>
<p>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</p>
<p> </p>
<h2 id="En_este_módulo">En este módulo</h2>
<ul>
<li><a href="/es/docs/Learn/Server-side/Django/Introducción">Introducción a Django</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/development_environment">Configurando un entorno de desarrollo Django</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Tutorial_local_library_website">Tutorial de Django: El sito web de la Biblioteca Local</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/skeleton_website">Tutorial de Django Parte 2: Creando el esqueleto de un sitio web</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Models">Tutorial de Django Parte 3: Usando modelos</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Admin_site">Tutorial de Django Parte 4: Sitio de administración de Django</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Home_page">Tutorial de Django Parte 5: Creando nuestra página de inicio</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Generic_views">Tutorial de Django Parte 6: Listas genéricas y vistas de detalle</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Sessions">Tutorial de Django Parte 7: Framework de sesiones</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Authentication">Tutorial de Django Parte 8: Autenticación de usuarios y permisos</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Forms">Tutorial de Django Parte 9: Trabajando con formularios</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Testing">Tutorial de Django Parte 10: Probando una aplicación web de Django</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/Deployment">Tutorial de Django Parte 11: Poniendo Django en producción</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/web_application_security">Seguridad en aplicaciones web Django</a></li>
<li><a href="/es/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li>
</ul>
|