Kitabı oku: «Django 2», sayfa 5
Crear un sistema de comentarios
Ahora va a elaborar un sistema de comentarios para el blog, donde los usuarios puedan comentar los artículos. Para crear este sistema, es necesario realizar los siguientes pasos:
1. Crear un modelo para guardar los comentarios.
2. Crear un formulario para enviar comentarios y validar la entrada de datos.
3. Añadir una vista que procese el formulario y guarde el nuevo comentario en la base de datos.
4. Editar la plantilla de detalle de artículos para mostrar un listado de comentarios y el formulario para añadir nuevos.
Empezará por crear un modelo para almacenar los comentarios. Para ello va a editar el fichero models.py de la aplicación blog con el siguiente código:

Este es el modelo Comment. Contiene una ForeignKey para asociar un comentario con un único artículo. Esta relación muchos a uno es definida en el modelo Comment porque cada comentario estará constituido por un único artículo, y cada artículo puede tener muchos comentarios. El atributo related_name permite nombrar el atributo que compone esta relación, desde el lado del objeto referenciado. Después de definirlo, puede recuperar los artículos de una instancia de comentario usando comment.post, y recuperar todos los comentarios a un artículo con post.comments.all(). Si no usara el parámetro related_name, Django usará el nombre del modelo en minúsculas seguido de _set (es decir, comment_set) para nombrar el gestor de la relación inversa.
Para obtener más información de las relaciones muchos a uno puede visitar https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_one/.
Ha añadido el campo booleano active para poder desactivar comentarios inapropiados de manera manual. El campo created lo utilizará para ordenar los comentarios de manera cronológica por defecto.
El nuevo modelo Comment que acaba de crear no está sincronizado con la base de datos. Va a ejecutar el siguiente comando para generar una nueva migración que refleje la creación de un nuevo modelo:

A continuación debería ver por salida:

Django ha generado el fichero 0002_comment.py dentro del directorio migrations/ de la aplicación blog. Ahora es necesario crear un esquema y aplicar los cambios a la base de datos. Para ello ejecute:

La salida obtenida debería ser semejante a esta:

La migración que acaba de crear se ha aplicado, apareciendo la tabla blog_comment.
Ya puede añadir el nuevo modelo al panel de administración para poder gestionar comentarios a través de esta sencilla interfaz. Abra el fichero admin.py de la aplicación blog, importe el modelo Comment y añada la clase ModelAdmin:

Arranque el servidor con el comando python manage.py runserver y abra la URL http://127.0.0.1:8000/admin/ en el navegador. En este punto debería aparecer la sección BLOG como se muestra en la imagen:

El modelo está registrado en el panel de administración y puede gestionar instancias Comment a través de la interfaz.
Crear formularios de un modelo
Seguirá necesitando elaborar un formulario para que los usuarios puedan escribir comentarios de un artículo del blog. Recuerde que Django dispone de dos clases base para construir formularios: Form y ModelForm. Ya ha utilizado la primera anteriormente para permitir a los usuarios compartir artículos vía email. En este caso, va a utilizar la clase ModelForm para crear un formulario dinámicamente a partir del modelo Comment. Edite el fichero forms.py de la aplicación blog con las siguientes líneas:

Para crear un formulario a partir de un modelo, necesitamos indicar qué modelo usar para la construcción del formulario en la clase Meta de este último. Django inspecciona el modelo y construye un formulario dinámicamente por usted. Cada tipo de campo del modelo tiene su correspondiente tipo de campos de formulario. La manera en que defina los campos de los modelos se tendrá en cuenta para la validación de los formularios. Por defecto, Django construye un campo de formulario por cada campo del modelo. Sin embargo, puede indicar explícitamente qué campos quiere incluir en el formulario con la lista fields o definir qué campos quiere excluir con la lista exclude. Para el formulario CommentForm, ha usado los campos name, email y body porque estos son los campos que los usuarios podrán rellenar.
Funcionamiento de ModelForms en vistas
La idea consiste en utilizar la vista de detalle de artículos para instanciar un formulario y procesarlo de una forma sencilla. Para ello, edite el fichero views.py, importe el modelo Comment y el formulario CommentForm, y modifique la vista post_detail del siguiente modo:


Revise lo que ha añadido a la vista. La vista post_detail muestra el artículo y los comentarios del mismo. Se ha añadido un QuerySet para recuperar todos los comentarios activos para este artículo con la siguiente sentencia:

Este QuerySet está construido a partir del objeto post. Se ha utilizado el gestor para objetos relacionados que se define como comments a través del atributo related_name de la relación con el modelo Comments.
Se ha usado la misma vista para permitir a los usuarios añadir nuevos comentarios. Por esta razón se inicializa la variable new_comment con valor None. Esta variable la utilizará con la creación de un nuevo comentario. En caso de que la petición sea tipo GET, se instancia un formulario con comment_form = CommentForm(). Si la petición es de tipo POST, la instanciación del formulario se realiza con los datos enviados y la validación se lleva a cabo con el método is_valid(). Si el formulario es inválido, en la renderización de la plantilla aparecerán la validación de errores. Si, por el contrario, es válido, se realizan las siguientes acciones:
1. Se crea un nuevo objeto Comment llamando al método save() del formulario y asignándolo a la variable new_comment:

El método save() crea una instancia del modelo con el que el formulario está relacionado y lo guarda en la base de datos. Si invocamos el método con el parámetro commit=False, se creará la instancia, pero no se salvará en la base de datos. Esto es útil cuando se quiere modificar el objeto antes de guardarlo definitivamente.
El método save() está disponible para ModelForm pero no para Form, ya que este último no está relacionado con un modelo.
2. Asignará el artículo actual al comentario que acaba de crear con:

3. Por último, guardará el nuevo comentario a través de la llamada al método save():

La nueva vista está lista para mostrar y procesar comentarios.
Añadir comentarios a la plantilla de detalle de artículos
Una vez creada la funcionalidad para gestionar comentarios para un artículo, es necesario adaptar la plantilla post/detail.html para:
• Mostrar el total de comentarios relacionados con artículos.
• Mostrar la lista de comentarios.
• Mostrar un formulario a los usuarios para añadir un comentario nuevo.
Lo primero que hará será añadir el número total de comentarios. Para ello editará la plantilla post/detail.html y añadirá el siguiente contenido al bloque content:

Para ello está usando el ORM de Django en la plantilla, ejecutando el método count() del QuerySet comments. El lenguaje de plantillas de Django no hace uso de paréntesis a la hora de llamar a métodos o funciones. El tag {% with %} permite asignar un valor a una nueva variable que estará disponible para usar dentro del bloque, hasta la etiqueta {% endtag %}.
La etiqueta de plantilla {% with %} es útil para evitar múltiples ejecuciones costosas dentro de la misma plantilla, por ejemplo, sobre la base de datos.
Utilizará el filtro de plantilla pluralize para mostrar el plural de la palabra comment en función del valor de total_comments. Este filtro funciona para múltiples idiomas, incluido el español. Los filtros de plantilla toman el valor de la variable a la que se aplican y la utilizan como entrada para generar una salida. Esto se tratará en el capítulo 3, Extensiones para el blog.
El filtro de plantilla pluralize devuelve una cadena con la letra “s” si el valor de entrada es diferente de 1. Los posibles textos renderizados serán 0 comments, 1 comment o N comments. Django incluye múltiples etiquetas y filtros de plantilla que nos ayudan a mostrar la información del modo en que necesitará.
A continuación, incluirá la lista de comentarios. Para ello añadirá en la plantilla post/detail.html las siguientes líneas:

La etiqueta {% for %} permite iterar sobre los comentarios. En caso de que la lista comments esté vacía, mostrará un comentario por defecto indicando al usuario que todavía no hay comentarios. Con la variable {{ forloop. counter }} enumerará los comentarios. Esta variable contiene un contador de bucle, indicando la iteración en que se encuentra. Por último, se muestra el nombre del usuario que realizó el comentario, la fecha y las primeras letras del cuerpo del comentario.
Para terminar, queda por mostrar un mensaje indicando que todo ha ido correctamente en caso de procesar bien el formulario, o el formulario en sí. Va a añadir las siguientes líneas al código anterior:

En caso de que el objeto new_comment exista, se muestra un mensaje satisfactorio indicando que el comentario se ha creado correctamente. En caso contrario, se muestra el formulario con un elemento párrafo <p> por cada campo, incluyendo el token CSRF necesario para las acciones POST. Va a abrir http://127.0.0.1:8000/blog/ en el navegador y pulsar sobre el título de un artículo para ver la página de detalle. Debería ver algo similar a esto:

Si añade varios comentarios a través del formulario, estos deberían aparecer bajo el artículo en orden cronológico:

Va a ir a http://127.0.0.1:8000/admin/blog/comment/ en el navegador. Verá la página de administración con la lista de comentarios que ha creado. Seleccione uno para editarlo. Desactive el check Active y pulse sobre el botón Save. El sistema le redirigirá a la lista de comentarios, y podrá ver que la columna Active se encuentra desactivada para el comentario que acaba de modificar. La pantalla debería verse de un modo similar a:

Si vuelve a la página de detalle del artículo, podrá ver que el comentario desactivado no se muestra. Tampoco se tiene en cuenta para la contabilización de comentarios. Gracias al campo active, puede desactivar comentarios inapropiados y evitar mostrarlos en los artículos.
Añadir funcionalidad de etiquetado
Tras implementar el sistema de comentarios, va a crear un modo de etiquetar los artículos. Esto lo hará integrando aplicaciones de Django de terceros en los proyectos. El módulo django-taggit es una aplicación reutilizable que ofrece un modelo Tag y un gestor que facilita añadir etiquetas a cualquier modelo. En https://github.com/alex/django-taggit puede echar un vistazo al código.
Lo primero que tiene que hacer es instalar django-taggit a través de pip con el siguiente comando:

Tras la instalación, va a editar el fichero settings.py del proyecto mysite e incluir la aplicación taggit en la lista INSTALLED_APPS:

A continuación, abra el fichero models.py de la aplicación blog y añada el gestor TaggableManager que ofrece django-taggit para relacionarlo con el modelo Post:

El gestor tags permite añadir, recuperar y eliminar etiquetas de un objeto Post.
Para generar las migraciones, va a ejecutar el siguiente comando para llevar a cabo los cambios en el modelo:

Obteniendo la siguiente salida:

Ahora, ejecute el siguiente comando para crear las tablas necesarias para los modelos de django-taggit y sincronizar los cambios que ha realizado en Post:

Obtendrá una salida indicando que las migraciones se han aplicado:

Con esto, la base de datos está lista para utilizar los modelos de django-taggit. A continuación, se detalla cómo usar el gestor tags. Para ello abra una consola y ejecute el comando python manage.py shell. Una vez tengamos el intérprete listo, ejecute las siguientes líneas de código para recuperar uno de los artículos (el que tiene id 1):

Ahora va a añadir algunas etiquetas y verificar que se han incorporado correctamente:

Por último, eliminará una etiqueta y verificar la lista de etiquetas:

Va a verificar esta misma información desde el panel de administración. Para ello, ejecute el servidor de desarrollo con python manage.py runserver y abra la URL http://127.0.0.1:8000/admin/taggit/tag/ en el navegador. Puede ver la página con la lista de objetos Tag de la aplicación taggit:

Vaya a http://127.0.0.1:8000/admin/blog/post/ y pulse sobre un artículo para poder editarlo. Los artículos ahora tienen un nuevo campo Tags, el cual se puede editar con facilidad:

Lo siguiente es modificar el blog para mostrar las etiquetas. Edite la plantilla blog/post/list.html y añada el siguiente código HTML tras el título del artículo:

El filtro de plantilla join funciona como el método join() de la clase string en Python, permitiendo concatenar elementos con la cadena dada. Navegará hasta http://127.0.0.1:8000/blog/ y debería ver algo similar a esto:

Va ahora a editar la vista post_list para poder mostrar a los usuarios todos los artículos con una etiqueta específica. Para ello, edite el fichero views.py de la aplicación blog, importando el modelo Tag de django-taggit y modificando la vista para que acepte, de forma opcional, un filtrado por etiqueta:

La vista post_list ahora funciona del siguiente modo:
1. Recupera un parámetro opcional, tag_slug, que por defecto tiene valor None. Este parámetro vendrá en la URL.
2. En la vista, se construye un QuerySet inicial, recuperando todos los artículos publicados. En caso de recibir un nombre de etiqueta, se recupera el objeto Tag correspondiente a través del atajo de Django get_object_or_404().
3. Con el objeto, filtrará la lista de artículos por aquellos que contengan la etiqueta mencionada. Dado que es una relación muchos-a-muchos, es necesario filtrar por las etiquetas contenidas en una lista, que, en este caso, es de un solo elemento.
Es necesario recordar que los QuerySets son perezosos, es decir, se ejecutan contra la base de datos solo cuando itere sobre la lista de artículos, cuando trate de renderizarlos o procesarlos de algún modo.
Por último, va a modificar la función render() abajo de la vista, para pasar la variable tag a la plantilla. La vista quedará finalmente así:

Queda modificar el fichero urls.py de la aplicación blog, comentar el patrón de URL de la vista basada en clase PostListView, y descomentar la vista post_list:

Añada también el patrón de URL para listar los artículos por etiqueta:

Como se puede ver, los dos patrones de URL utilizan la misma vista, pero los ha nombrado de un modo diferente. El primer patrón llama a la vista post_list sin ningún parámetro opcional, mientras que el segundo llama a la vista con el parámetro tag_slug. Esto indica que el parámetro es de tipo slug, de forma que sea una cadena de texto en minúsculas, formado por letras y/o números ASCII, y que pueda contener guiones medios y/o bajos.
Ahora que vuelve a usar la vista post_list, es necesario modificar la paginación para usar el objeto post dentro de la plantilla blog/post/list.html:

Añada las siguientes líneas antes de bucle {% for %}:

Cuando los usuarios accedan al blog verán la lista de todos los artículos, y si filtran por etiqueta, verán los que contengan la etiqueta seleccionada. Va ahora a modificar la forma en que se muestran las etiquetas:

Ahora iterará sobre todas las etiquetas del artículo, mostrando por cada una de ellas un enlace para filtrar los artículos por dicha etiqueta. Ha construido la URL con {% url "blog:post_list_by_tag" tag.slug %}, con el nombre de la URL y la etiqueta slug como parámetros.
Abra http://127.0.0.1:8000/blog/ en el navegador y pinche sobre cualquier etiqueta. Esto nos mostrará todos los artículos con dicha etiqueta:

Recuperar artículos por similitud
Una vez implementado el sistema de etiquetado en el blog, puede utilizarlo para hacer cosas interesantes con él. A través de las etiquetas, puede clasificar de forma muy específica el contenido del blog. Artículos de temas relacionados tendrán etiquetas comunes. Va a construir una funcionalidad para mostrar artículos similares según la cantidad de etiquetas en común. De esta forma, cuando un usuario lea un artículo, puede sugerirle leer otros con contenido relacionado.
Para recuperar los artículos similares a otro artículo, es necesario realizar los siguientes pasos:
1. Recuperar todas las etiquetas del artículo actual.
2. Recuperar todos los artículos que contengan algunas de las etiquetas previamente recuperadas.
3. Excluir el artículo actual de la lista anterior para evitar recomendar el mismo artículo.
4. Ordenar los resultados por número de etiquetas en común con el artículo actual.
5. En caso de que uno o más artículos contengan el mismo número de etiquetas, se recomienda el más reciente.
6. Limitar la consulta al número de artículos que quiere recomendar.
Estos pasos se trasladan a consultas complejas que incluirá en la vista post_detail. Va a editar el fichero views.py y añadir la siguiente línea de importación al comienzo del fichero:

Esta es la función de agregación Count del ORM de Django. Esta función permite realizar contabilizaciones agregadas de etiquetas. El módulo django.db.models incluye las siguientes funciones de agregación:
• Avg: The value average
• Max: The maximum value
• Min: The minimum value
• Count: The objects count
Para profundizar sobre la agregación visita se recomienda visitar https://docs.djangoproject.com/en/2.0/topics/db/aggregation/.
Añadirá las siguientes líneas dentro de la vista post_detail antes de la función render(), con el mismo nivel de indentación:

El código anterior funciona del siguiente modo:
1. Se recupera una lista Python de identificadores de las etiquetas del artículo actual. El QuerySet values_list() devuelve tuplas con los valores de los campos especificados. Al pasar el parámetro flat=True, aplana la estructura, resultando: [1, 2, 3, ...].
2. Se recuperan todos los artículos que contengan cualquiera de las etiquetas, excluyendo el artículo actual.
3. Utilizará la agregación Count para generar un campo calculado (same_tags) que contiene el número de etiquetas compartidas de cada artículo con el actual.
4. Se ordenan los resultados por número de etiquetas compartidas (orden descendente) y por el campo publish, para mostrar primero los artículos más recientes en caso de coincidir en número de etiquetas en común. Se limitará a recuperar solo los 4 primeros artículos.
Añadirá el objeto similar_posts al contexto del diccionario de la función render() del siguiente modo:

Va ahora a editar la plantilla blog/post/detail.html y añadir el siguiente código antes de los comentarios del artículo:

Ahora nuestra página de detalle de artículo debería ser semejante a:

Ahora ya puede recomendar artículos a los usuarios. django-taggit también tiene un gestor, similar_objects(), que puede utilizar para recuperar objetos similares que contengan etiquetas en común. Para obtener más información sobre cómo funciona el gestor visite https://djangotaggit.readthedocs.io/en/latest/api.html.
Como último detalle, también puede añadir la lista de etiquetas de la vista de detalle a la plantilla blog/post/list.html.