Crear etiquetas y filtros de plantilla personalizados.
Django nos ofrece una gran variedad de etiquetas de plantillas, tales como {% if %} or {% block %}. Las hemos usado a lo largo de todos los capítulos anteriores. Puedes encontrar una referencia completa de las etiquetas de plantillas en https://docs.djangoproject.com/en/4.2/ref/templates/builtins/.
Pero Django también nos permite personalizar estas etiquetas de plantillas a nuestra necesidad. Es muy útil cuando necesitamos una funcionalidad que no esté contemplada por las etiquetas de Django. Siguiendo con nuestro proyecto podríamos crear una etiqueta para mostrar la lista de los últimos post que hayan sido publicados en el blog. Podríamos ponerla en un lateral de la página de forma que siempre este visible.
Creando etiquetas de plantilla personalizadas.
Django nos proporciona las siguientes funciones auxiliares que nos permiten crear fácilmente etiquetas personalizadas.
- simple_tag: Procesa los datos que la facilitemos y devuelve un string.
- inclusion_tag: Procesa los datos que le facilitemos y devuelve una plantilla renderizada.
Implementando una etiqueta de plantilla "simple".
Vamos a empezar creando una etiqueta sencilla que nos devuelva el número total de post publicados en el blog.
Edita el archivo templatetags/blog_tags.py que acabamos de crear y añade el siguiente código.
PracticaDjango/Blog/templatetags/blog_tags.py
from django import template
from ..models import Post
register = template.Library()
@register.simple_tag
def posts_totales():
return Post.objects.count()
Hemos creado nuestra primera etiqueta que devuelve el numero total de post.
Cada módulo que contenga etiquetas de plantilla necesita definir una variable llamada register para que sea una etiqueta válida de la librería de Django. Esta variable es una instancia de template.Library(), y se usará para registrar todas las etiquetas y filtros de la aplicación.
En el código que acabamos de escribir, hemos definido una etiqueta llamada posts_totales a través de una simple función de Python. Hemos añadido el decorador @register.simple_tag a la función para registrarla como una etiqueta. Djando usará el nombre de la función como nombre de la etiqueta. Si quieres registrarla usando un nombre diferente, entonces debes especificar el nuevo nombre en un atributo de esta forma: @register.simple_tag(name='mi_etiqueta').
PracticaDjango/Blog/templates/Blog/lista.html
<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}
<!-- Cargamos las etiquetas personalizada -->
{% load blog_tags %}
<!-- Establecemos el titulo de la página -->
{% block title %}Blog{% endblock %}
{% block content %}
<div class="container-fluid bg-white" style="margin-bottom: 50px;">
<h1 class="text-center">Listado de Post publicados</h1>
<div id="sidebar">
<h2>El Blog</h2>
<p>
Hay escritos {% posts_totales %} posts hasta el momento.
</p>
</div>
{% for post in posts %}
...
PracticaDjango/Proyecto_web_app/static/Proyecto_web_app/css/style.css
...
#sidebar {
float:right;
width:30%;
padding:10px;
background:#efefef;
height:100%;
}
Implementando una etiqueta de plantilla "inclusion".
Vamos a crear otra etiqueta para mostrar los últimos post que se han publicado en el blog. Esta vez utilizaremos una etiqueta de plantilla "inclusion". Usando este tipo de etiquetas podemos devolver al usarla una plantilla con sus variables de contexto incluidas.
Edita el archivo templatetags/blog_tags.py y añade el siguiente código:
PracticaDjango/Blog/templatetags/blog_tags.py
#...
@register.inclusion_tag("Blog/ultimos_posts.html")
def mostrar_ultimos_posts(contador=5):
ultimos_posts = Post.objects.order_by('-created')[:contador]
return {'ultimos_posts': ultimos_posts}
PracticaDjango/Blog/templates/Blog/ultimos_posts.html
<ul>
{% for post in ultimos_posts %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.titulo }}</a>
</li>
{% endfor %}
</ul>
<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}
<!-- Cargamos la etiqueta personalizada -->
{% load blog_tags %}
<!-- Establecemos el titulo de la página -->
{% block title %}Blog{% endblock %}
{% block content %}
<div class="container-fluid bg-white" style="margin-bottom: 50px;">
<h1 class="text-center">Listado de Post publicados</h1>
<div id="sidebar">
<h2>El Blog</h2>
<p>
Hay escritos {% posts_totales %} posts hasta el momento.
</p>
<h3>Últimos Posts</h3>
{% mostrar_ultimos_posts 3 %}
</div>
{% for post in posts %}
<h2>
<a href="{{ post.get_absolute_url }}">{{post.titulo}}</a>
</h2>
<p class="date">Publicado {{ post.autor }} por {{ post.updated }} </p>
{{ post.contenido|truncatewords:30|linebreaks }}
{% endfor %}
</div>
<!-- {% include "Proyecto_web_app/paginacion.html" with page=posts %} -->
{% include "Proyecto_web_app/paginacion.html" with page=page_obj %}
<section>
<div style="width: 75%; margin: auto; color: white; text-align: center;">
Categorías:
{% for categoria in categorias %}
<a href="{% url 'Blog:categoria' categoria.id %}" class="link-info">
<span style="color:rgb(2, 7, 59)">{{ categoria.nombre }}</span>
</a>
{% endfor %}
</div>
</section>
{% endblock %}
Creando una etiqueta de plantilla que devuelva un conjunto de consultas.
Finalmente crearemos una etiqueta "simple" que devuelva un valor. Guardaremos el resultado en una variable que podamos reutilizar, en vez de mostrarla directamente. Crearemos una etiqueta para mostrar el post más comentado.
Edita el archivo templatetags/blog_tags.py y añade la siguiente importación y esta etiqueta de plantilla al mismo.
PracticaDjango/Blog/templatetags/blog_tags.py
@register.simple_tag def obtener_posts_mas_comentados(contador=5): return Post.objects.annotate(comentarios_totales=Count("comentarios")).order_by( "-comentarios_totales" )[:contador]
<!--Cargamos la plantilla base--> {% extends "Proyecto_web_app/base.html" %} <!-- Cargamos la etiqueta personalizada --> {% load blog_tags %} <!-- Establecemos el titulo de la página --> {% block title %}Blog{% endblock %} {% block content %} <div class="container-fluid bg-white" style="margin-bottom: 50px;"> <h1 class="text-center">Listado de Post publicados</h1> <div id="sidebar"> <h2>El Blog</h2> <p> Hay escritos {% posts_totales %} posts hasta el momento. </p> <h3>Últimos Posts</h3> {% mostrar_ultimos_posts 3 %} <h3>Post más comentados</h3> {% obtener_posts_mas_comentados as posts_mas_comentados %} <ul> {% for post in posts_mas_comentados %} <li> <a href="{{ post.get_absolute_url }}">{{ post.titulo }}</a> </li> {% endfor %} </ul> </div> {% for post in posts %} <h2> <a href="{{ post.get_absolute_url }}">{{post.titulo}}</a> </h2> <p class="date">Publicado {{ post.autor }} por {{ post.updated }} </p> {{ post.contenido|truncatewords:30|linebreaks }} {% endfor %} </div> <!-- {% include "Proyecto_web_app/paginacion.html" with page=posts %} --> {% include "Proyecto_web_app/paginacion.html" with page=page_obj %} <section> <div style="width: 75%; margin: auto; color: white; text-align: center;"> Categorías: {% for categoria in categorias %} <a href="{% url 'Blog:categoria' categoria.id %}" class="link-info"> <span style="color:rgb(2, 7, 59)">{{ categoria.nombre }}</span> </a> {% endfor %} </div> </section> {% endblock %}
Implementando filtros personalizados.
Creando un filtro de plantilla para utilizar el método de Escritura MARKDOWN
Vamos a ver como crear un filtro personalizado que nos permita usar MARKDOWN en las publicaciones de nuestro Blog y luego convertir el cuerpo del post en lenguaje HTML en las plantillas.
MARKDOWN es un método de escritura de texto plano que es muy sencilla de usar y está pensado para que luego pueda convertirse en lenguaje HTML. Aprender la sintexis de MARKDOWN es mucho más fácil que aprender HTML. Podemos aprender el uso básico de MARKDOWN en el siguiente enlace https://markdown.es/.
Después edita el archivo templatetags/blog_tags.py e incluye el siguiente código:
PracticaDjango/Blog/templatetags/blog_tags.py
#...
# Para utilizar markdown en el cuerpo del post.
from django.utils.safestring import mark_safe
import markdown
#...
@register.filter(name='markdown')
def markdown_format(texto):
return mark_safe(markdown.markdown(texto))
Registramos el filtro de la plantilla igual que registramos las etiquetas. Para prevenir un conflicto de nombres entre el nombre de la función y el nombre del filtro, hemos llamado a la función markdown_format y a el filtro 'markdown' usando el atributo name, para poder usarlo en las plantillas como {{ variable|markdown }}
PracticaDjango/Blog/templates/Blog/detalle.html
{% extends "Proyecto_web_app/base.html" %} {% load blog_tags %} {% block title %}Detalle de un Post{% endblock %} {% block content %} <div class="container-fluid bg-white" style="margin-bottom: 150px;"> <h1>{{ post.titulo }}</h1> <p class="date">Publicado {{ post.autor }} por {{ post.updated }} </p> {{ post.contenido|markdown }} {% with comentarios.count as total_comentarios %} <h2> {{ total_comentarios }} comentario{{ total_comentarios|pluralize }} </h2> {% endwith %} {% for comentario in comentarios %} <div class="comment"> <p class="info"> Comentario {{ forloop.counter }} de {{ comentario.autor }} - {{ comentario.created }} </p> {{ comentario.cuerpo|linebreaks }} </div> {% empty %} <p>No hay comentarios aún.</p> {% endfor %} <!-- Contenedor para centrar el botón de regresar a la lista de posts --> <div class="d-flex justify-content-center align-items-center"> <a href="{% url 'Blog:lista_post' %}" class="btn btn-primary">Regresar</a> </div> {% include "Blog/form_comentario.html" %} </div> {% endblock %}
PracticaDjango/Blog/templates/Blog/lista.html
<!--Cargamos la plantilla base--> {% extends "Proyecto_web_app/base.html" %} <!-- Cargamos la etiqueta personalizada --> {% load blog_tags %} <!-- Establecemos el titulo de la página --> {% block title %}Blog{% endblock %} {% block content %} <div class="container-fluid bg-white" style="margin-bottom: 50px;"> <h1 class="text-center">Listado de Post publicados</h1> <div id="sidebar"> <h2>El Blog</h2> <p> Hay escritos {% posts_totales %} posts hasta el momento. </p> <h3>Últimos Posts</h3> {% mostrar_ultimos_posts 3 %} <h3>Post más comentados</h3> {% obtener_posts_mas_comentados as posts_mas_comentados %} <ul> {% for post in posts_mas_comentados %} <li> <a href="{{ post.get_absolute_url }}">{{ post.titulo }}</a> </li> {% endfor %} </ul> </div> {% for post in posts %} <h2> <a href="{{ post.get_absolute_url }}">{{post.titulo}}</a> </h2> <p class="date">Publicado {{ post.autor }} por {{ post.updated }} </p> <!-- {{ post.contenido|truncatewords:30|linebreaks }} --> {{ post.contenido|markdown|truncatewords_html:15}} {% endfor %} </div> <!-- {% include "Proyecto_web_app/paginacion.html" with page=posts %} --> {% include "Proyecto_web_app/paginacion.html" with page=page_obj %} <section> <div style="width: 75%; margin: auto; color: white; text-align: center;"> Categorías: {% for categoria in categorias %} <a href="{% url 'Blog:categoria' categoria.id %}" class="link-info"> <span style="color:rgb(2, 7, 59)">{{ categoria.nombre }}</span> </a> {% endfor %} </div> </section> {% endblock %}
Hemos realizado lo mismo que en el archivo anterior. Solamente hemos usado "truncate_html" en vez de "truncate" porque el código generado ya es html.
Ahora abre el navegador, ve a la consola de administración de Django y añade un nuevo post con el siguiente contenido:
Este es un post usando markdown
--------------------------------------
*Este texto esta en cursiva* y **este en negrita**.
Aquí hay una lista:
- Uno
- Dos
- Tres
Y un [link to the Django website](https://www.djangoproject.com/).
No hay comentarios:
Publicar un comentario