En el capítulo anterior, aprendimos los componentes principales de Django desarrollando un blog dentro de la aplicación principal. Creamos una aplicación de blog sencilla utilizando vistas, plantillas y URLs. En este capítulo, ampliaremos las funcionalidades de la aplicación del blog con características nuevas.
Usando URLs canónicas para los modelos.
PracticaDjango/Blog/models.py:
from django.db import models from django.contrib.auth.models import User # Para importar el usuario que esta creando el post from django.utils import timezone # Para importar el timezone y establecer la fecha de creación. from django.urls import reverse # Para crear la Url canónica # Create your models here. class Categoria(models.Model): """Diferentes tags para asignar a los post""" nombre = models.CharField(max_length=50) created = models.DateTimeField(default=timezone.now) updated = models.DateTimeField(auto_now=True) def __str__(self): return self.nombre class Post(models.Model): titulo = models.CharField(max_length=250) slug = models.CharField(max_length=250) contenido = models.TextField() autor = models.ForeignKey(User, on_delete=models.CASCADE, related_name="blog_posts") categorias = models.ManyToManyField(Categoria, null=True, blank=True) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) # Model meta Options - https://docs.djangoproject.com/en/4.1/topics/db/models/#meta-options class Meta: verbose_name = "post" verbose_name_plural = "posts" ordering = ['-updated'] indexes = [ models.Index(fields=['updated']), ] def __str__(self): return self.titulo def get_absolute_url(self): return reverse("Blog:detalle_post", args=[self.id])
<a href="{% url 'Blog:detalle_post' post.id %}">{{post.titulo}}</a>
<a href="{{ post.get_absolute_url }}">{{post.titulo}}</a>
PracticaDjango/Blog/templates/Blog/lista.html
<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}
<!-- 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>
{% 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>
...
PracticaDjango/Blog/templates/Blog/categoria.html
{% block content %}
<div class="container-fluid bg-white" style="margin-bottom: 50px;">
<h1 class="text-center">Listado de Post publicados por "{{categoria}}"</h1>
{% 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>
<!-- Contenedor para centrar el botón de regresar a la lista de posts -->
Creando URL amigables para los SEO de las publicaciones.
PracticaDjango/Blog/models.py:
...
class Post(models.Model):
...
slug = models.CharField(max_length=250, unique_for_date='created')
...
$ python manage.py makemigrations Blog $ python manage.py migrate
Modificando los patrones de las URLs.
PracticaDjango/Blog/urls.py:
path('<int:id>/', views.detalle_post, name='detalle_post'),
PracticaDjango/Blog/urls.py:
path('<int:year>/<int:month>/<int:day>/<slug:post>/',
views.detalle_post,
name='detalle_post'),
Modificando las vistas.
PracticaDjango/Blog/views.py
...
def detalle_post(request, year, month, day, post):
"""Muestra todos los post"""
post = get_object_or_404(Post,
slug=post,
created__year=year,
created__month=month,
created__day=day)
return render(request, 'Blog/detalle.html', {'post': post})
...
Modificar la URL canónica para las post.
PracticaDjango/Blog/models.py
class Post(models.Model):
...
def get_absolute_url(self):
return reverse("Blog:detalle_post",
args=[self.created.year,
self.created.month,
self.created.day,
self.slug])
Añadiendo Paginación para la lista de Post.
Edita el archivo de vistas de la aplicación blog "views.py" y modifica la vista "detalle_post" de la siguiente manera:
PracticaDjango/Blog/views.py
from django.shortcuts import render, get_object_or_404, get_list_or_404
from .models import Post, Categoria
from django.core.paginator import Paginator
# Create your views here.
def lista_post(request):
posts = get_list_or_404(Post)
categorias = get_list_or_404(Categoria)
# Paginación con 3 post por página.
paginador = Paginator(posts, 3)
numero_pagina = request.GET.get('page', 1)
posts_por_pagina = paginador.page(numero_pagina)
return render(
request, "Blog/lista.html", {"posts": posts_por_pagina, "categorias": categorias}
)
- Hemos instanciado la clase 'Paginator' pasándole como argumentos los post y el número de objetos a devolver por página.
- Hemos recuperado el número de la página a mostrar del GET del request. Como funciona como un diccionario le pedimos que recupere el número de página y si no existiera que nos devuelva 1, que será el valor por defecto que cargará la primera página de los resultados.
- Obtenemos los objetos para cada página llamando al método 'page' de paginacion. Este método devuelve un objeto que se guardara en la variable posts_por_pagina.
- Pasamos a la plantilla este objeto que contendrá el número de página y los post que han de mostrarse en ella.
Creando la plantilla para la paginación.
PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/paginacion.html
<div>
<ul class="pagination">
{% if page.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page.previous_page_number }}">
Anterior
</a>
</li>
{% endif %}
<li class="page-item">
<a class="page-link" href="#">
Página {{ page.number }} de {{ page.paginator.num_pages }}
</a>
</li>
{% if page.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page.next_page_number }}">
Siguiente
</a>
</li>
{% endif %}
</ul>
</div>
PracticaDjango/Blog/templates/Blog/lista.html
<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}
<!-- 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>
{% 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 %}
<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 %}
Manejando errores de paginación.
PracticaDjango/Blog/views.py
from django.shortcuts import render, get_object_or_404, get_list_or_404
from .models import Post, Categoria
from django.core.paginator import Paginator, EmptyPage
# Create your views here.
def lista_post(request):
posts = get_list_or_404(Post)
categorias = get_list_or_404(Categoria)
# Paginación con 3 post por página.
paginador = Paginator(posts, 3)
numero_pagina = request.GET.get('page', 1)
try:
posts_por_pagina = paginador.page(numero_pagina)
except EmptyPage:
# Si la página está fuera del rango de las paginas de resultados
posts_por_pagina = paginador.page(paginador.num_pages)
return render(
request, "Blog/lista.html", {"posts": posts_por_pagina, "categorias": categorias}
)
...
PracticaDjango/Blog/views.py
from django.shortcuts import render, get_object_or_404, get_list_or_404
from .models import Post, Categoria
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
# Create your views here.
def lista_post(request):
posts = get_list_or_404(Post)
categorias = get_list_or_404(Categoria)
# Paginación con 3 post por página.
paginador = Paginator(posts, 3)
numero_pagina = request.GET.get('page', 1)
try:
posts_por_pagina = paginador.page(numero_pagina)
except EmptyPage:
# Si la página está fuera del rango de las paginas de resultados
posts_por_pagina = paginador.page(paginador.num_pages)
except PageNotAnInteger:
# Si el párametro introducido no es un número entero
posts_por_pagina = paginador.page(1)
return render(
request, "Blog/lista.html", {"posts": posts_por_pagina, "categorias": categorias}
)
...
Construir las vistas usando clases.
- Organizar el código relacionado con los métodos HTTP, como GET, POST o PUT, en métodos separados en lugar de utilizar ramificaciones condicionales.
- Utilizar la herencia múltiple para crear clases de vista reutilizables (también conocidas como mezclas).
Usar una vista basada en clases para listar los Post.
PracticaDjango/Blog/views.py
from django.shortcuts import render, get_object_or_404, get_list_or_404
from .models import Post, Categoria
# from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.views.generic import ListView
# Create your views here.
class lista_post(ListView):
"""
Una alternativa a la función lista_post
"""
model = Post
context_object_name = 'posts'
paginate_by = 3
template_name = "Blog/lista.html"
...
- model = 'Post' especifica que la vista trabajará con el modelo 'Post'. Podríamos haber utilizado una expresión análoga como queryset = Post.objects.all(), pero esta expresión es mucho más sencilla.
- Usamos la variables de contexto 'posts' que es la que están utilizando las plantillas para mostar los resultados de la consulta. La variable que se usa por defecto es object_list si tu no especificas ningún context_object_name.
- Especificamos la paginación con la propiedad paginate_by para decirle a Django que muestre tres objetos por página.
- Usaremos la plantilla personalizada para renderizar la página usando template_name. Si no especificas ninguna plantilla por defecto Django buscará la plantilla en /Blog/lista_post.html, es decir dentro de template en /nombre_aplicacion/nombre_clase.html
PracticaDjango/Blog/urls.py
from django.urls import path
from . import views
app_name = 'Blog'
urlpatterns = [
# post views
# path('', views.lista_post, name='lista_post'),
path('', views.lista_post.as_view(), name='lista_post'),
path('<int:year>/<int:month>/<int:day>/<slug:post>/', views.detalle_post, name='detalle_post'),
path('categoria/<int:categoria_id>/', views.categoria, name='categoria'),
]
PracticaDjango/Blog/templates/Blog/lista.html
...
<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 %}
...
PracticaDjango/Blog/views.py
from django.shortcuts import render, get_object_or_404, get_list_or_404
from .models import Post, Categoria
# from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.views.generic import ListView
# Create your views here.
class lista_post(ListView):
"""
Una alternativa a la función lista_post
"""
model = Post
context_object_name = 'posts'
paginate_by = 3
template_name = "Blog/lista.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categorias'] = Categoria.objects.all()
return context
...