jueves, 25 de mayo de 2023

16.- Creación de la aplicación BLOG

En este capítulo crearemos un blog para que la gente pueda escribir mensajes sobre nuestra empresa y servicios.

 - creacion de blog:

    $ python manage.py startapp Blog

- creación del modelo. 

Las entradas del blog van a tener un titulo, un contenido , una imagen y luego tanto para las categorías del blog como para la información del post una fecha de creación y otra de actualización. Para ello abrimos el archivo models.py de esta nueva aplicación del blog y creamos los modelos 'Categoria' y 'Post'. Es muy parecido a como hicimos para la aplicación servicio.

Empecemos con la creación del modelo para las categorías. Estas son las diferentes etiquetas que se pueden aplicar a cada post. En un post puede haber una o varias de estas etiquetas y cada una de las etiquetas puede ser aplicada a uno o a varios post. Esto en SQL es lo que se conoce como una relación ManyToMany. Este tipo de relaciones en SQL se suelen resolver creando una tabla intermedia (post_categorias por ejemplo) pero Django nos permite hacerlo de una forma más sencilla como veremos a continuación.

Creación del modelo Categoría.

PracticaDjango/Blog/models.py: 

from django.db import models

from django.utils import timezone

# 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

Este es el modelo de datos que utilizaremos para registrar las diferentes categorías de los post. Cada categoría que podamos aplicar tendrá un nombre que identifique la etiqueta y una fecha de creación y de actualización. Veamos los campos que componen este modelo:

  • nombre: este es el campo para las etiquetas que se aplicarán a las diferentes publicaciones. Es un campo CharField que se traduce en una columna VARCHAR en la base de datos SQL.
  • created: este el campo que registrará la fecha de creación de las categorías. Es un campo DateTimeField que utilizaremos para crear la fecha y hora en que el objeto fue creado. Al usar default = timezone.now, se establecerá como valor de este campo la fecha de nuestra zona horaria en el momento de su creación. Esta variable se puede modificar posteriormente si fuese necesario a tráves del panel de administración de Django.
  • updated: es un campo DateTimeField. Lo usaremos para registrar la última fecha y hora cuando se actualice el post. Usando auto_now, la fecha y hora se actualizarán automáticamente cuando se guarde el objeto.


Creación del modelo Post.

PracticaDjango/Blog/models.py: 

from django.db import models

from django.utils import timezone

from django.contrib.auth.models import User
# Para importar el usuario que esta creando el post

# 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)

    def __str__(self):
        return self.titulo

Este es el modelo de datos que utilizaremos para mostrar las publicaciones del blog. Las publicaciones tendrán un título, una etiqueta corta y un cuerpo donde escribir el mensaje. Echemos un vistazo a los campos de este modelo:

  • titulo: este es el campo para el título de la publicación. Es un campo CharField que se traduce en una columna VARCHAR en la base de datos SQL.
  • slug: este es un campo SlugField que se traduce en una columna VARCHAR en la base de datos SQL. Un slug es una etiqueta corta que solo contiene letras, números, guiones bajos o guiones. Una publicación con el título "NBA2023: el video juego" podría tener un slug como "nba2023-video-juego". Usaremos el campo slug para crear URLS que sean amigables para los buscadores.
  • contenido: este el campo donde se almacenará el contenido del mensaje que se quiera publicar. Es un campo TextField que se traducirá en una columna TEXT en la base de datos SQL.
  • autor: es un campo que vamos a sacar de otra tabla de una base de datos ya existente en Django que nos dice que usuario esta conectado ese momento a la sesión. Es una relación 'One to Many' de SQL, puesto que un autor puede escribir varios post, pero en el que cada post solo puede tener un creador y no varios. Para saber quien es el usuario conectado importamos la tabla User desde django.contrib.auth.models. Además usamos el argumento on_delete=models.CASCADE para que si un usuario se da de baja de nuestro sitio web se borren todos los 'post' que haya publicado. Usamos related_name para especificar un nombre para la relación inversa, de autor a post. Esto nos permitirá acceder fácilmente a todos los post que haya escrito un usuario usando la notación User.blog_post lo que nos facilitará obtener todos los post que haya escrito un determinado autor. Si no especificamos un related_name, Django creará uno por defecto en este tipo de campos combinando el nombre del modelo en minúsculas y el sufijo _set. (post_set)
Un caso aparte es el campo categorias. Este campo es un campo con una relación many-to-many ya que una categoría puede ser asignada a varios post distintos, pero a la vez un post puede tener diferentes categorías. Cuando se crea un campo ManyToManyField, Django crea una tabla intermedia usando las claves privadas de ambos modelos. El campo ManytoManyField puede ser definido en cualquiera de los dos modelos relacionados. Puedes obtener más relación a este tema en https://docs.djangoproject.com/en/4.2/topics/db/examples/many_to_many/.

También hemos agregado un método str() a la clase del modelo este es el método predeterminado en Python para devolver una cadena con la representación legible del objeto. Django utilizará este método para mostrar el nombre del objeto en muchos lugares, como el sitio de administración de Django.


Añadiendo campos Datetime.


Cada post habrá sido creado en una determinada fecha y hora. También cada post podrá ser modificado posteriormente a una determinada fecha y hora. Para ello necesitaremos añadir los siguientes campos 

Hemos añadido los siguientes campos al modelo Post:

  • created: es un campo DateTimeField. Lo usaremos para guardar la fecha y hora en que el post fue creado. A diferencia del campo con el mismo nombre que usamos en el modelo Categoría, al usar auto_now_add, la fecha y hora se guardarán automáticamente al crear el objeto.
  • updated: es un campo DateTimeField. Lo usaremos para registrar la última fecha y hora cuando se actualice el post. Usando auto_now, la fecha y hora se actualizarán automáticamente cuando se guarde el objeto.

Creando una clase Meta para el modelo Post.

Los post de un blog normalmente se suelen mostrar en un orden cronológico inverso, es decir, primero lo más recientes y luego los más antiguos. Para conseguir esto vamos a definir una forma de ordenación para nuestro modelo. Este orden por defecto se aplicará cuando obtengamos objetos de la base de datos cuyo orden no este especificado en la consulta. 

Edita el archivo models.py de la aplicación Blog para que tenga el siguiente código. Las líneas nuevas aparecen resaltadas.

PracticaDjango/Blog/models.py: 

from django.db import models

from django.utils import timezone

from django.contrib.auth.models import User
# Para importar el usuario que esta creando el post

# 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="autor")
    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]

    def __str__(self):
        return self.titulo
Hemos añadido una clase Meta dentro del modelo. Esta es la clase que define datos adicionales o Metadatos para el modelo. Usamos el atributo o propiedad 'ordering' para decirle a Django que debería ordenar los post por la fecha de actualización. Este orden se aplicará por defecto a todas las consultas que no contengan un criterio de ordenación específico. Le indicamos que queremos los resultados en orden descendente usando un guión '-' delante del nombre del campo (-updated). De esta forma los post serán mostrados en orden cronológico inverso por defecto (primero los más recientes y luego los más antiguos)

Añadiendo un índice a la base de datos. 

Vamos a crear un índice para poder indexar la base de datos por el campo 'updated'. Esto mejorará el rendimiento para las consultas en las que se utilice un filtro para mostrar los resultados en base a este campo. Cuando haya muchos post esperamos que esto mejore el rendimiento ya que por defecto hemos establecido anteriormente que los resultados se muestren por la fecha de actualización.

Edita el archivo models.py de la aplicación Blog para que tenga el siguiente código. Las líneas nuevas aparecen resaltadas.

PracticaDjango/Blog/models.py: 

from django.db import models

from django.utils import timezone

from django.contrib.auth.models import User
# Para importar el usuario que esta creando el post

# 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="autor")
    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

Hemos agregado el atributo 'indexes' a la clase Meta del modelo Post. Esta opción nos permite crear índices o indexar la base de datos, utilizando uno o varios campos, en orden ascendente o descendente. Para ello hemos utilizado el campo '-updated'. La creación de este índice se incluirá en la migración de la base de datos que generaremos a continuación para nuestros modelos.

nota: El ordenamiento de índices no es compatible en MySQL. Si utilizas MySQL como base de datos, se creará un índice descendente como un índice normal.


- registrar la aplicación.

Necesitamos activar la aplicación del Blog en el proyecto, para que Django rastree la aplicación y se a posible crear las tablas en la base de datos de los modelos. Para ello nos vamos al archivo settings.py que recoge la configuración general del proyecto y que esta dentro del directorio PracticaDjango, buscamos el apartado INSTALLED_APPS y registramos la aplicación:

PracticaDjango/PracticaDjango/settings.py: 

...
# Application definition

INSTALLED_APPS = [
    # my applications
    'Proyecto_web_app',
    'Servicios',
    'Blog',
    # third party applications
    'bootstrap5',
    # default aplications
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [

- migraciones de la base de datos

$ python manage.py makemigrations Blog

$ python manage.py migrate

Django habrá creado el archivo 0001_initial.py dentro del directorio 'migrations' de la aplicación 'Blog'. Si estas interesado en ver el código SQL que ha generado la base de datos puedes usar la instrucción siguiente:

python manage.py sqlmigrate blog 0001


- preparar el panel de administración para meter los datos.


Aunque ya comentamos de pasada este punto cuando creamos la aplicación "Servicios" en un post anterior, vamos a detenernos a explicarlo más en detalle.

Ahora que tenemos el modelo Post sincronizado con la base de datos, vamos a crear un sencillo sitio de administración desde donde gestionar los post del blog.

Django ya viene de serie con una interfaz de administración que es muy útil para editar el contenido. El sitio de administración de Django se constuye dinámicamente leyendo los metadatos del modelo y proporcionando una interfaz lista para usar en producción y poder editar el contenido. Puedes usarlo tal cual viene o configurarlo para que muestre de la forma que tu quieras la información de tus modelos en el. La aplicación 'django.contrib.admin' ya está incluida entre las aplicaciones instaladas por defecto, por lo que no necesitas añadirla. 

Vamos a registrar estos modelos que hemos creado en el panel de administración.

Abrimos el archivo admin.py de la aplicación Blog y registramos lo que queremos que se muestre en el panel de administración:

PracticaDjango/Blog/templates/Blog/admin.py

from django.contrib import admin

# Register your models here.
# Importar del modelo tanto la categoría como el post
from .models import Categoria, Post

class Categoria_Admin(admin.ModelAdmin):
    # Campos de solo lectura en el panel de administración.
    readonly_fields = ('created', 'updated')

class Post_Admin(admin.ModelAdmin):
    # Campos de solo lectura en el panel de administración.
    readonly_fields = ('created', 'updated')

# Registramos todo lo anterior.
admin.site.register(Categoria, Categoria_Admin)
admin.site.register(Post, Post_Admin)

Creando un Superusuario.

Si ya creaste anteriormente un superusuario en el post de como crear la aplicación 'Servicios' no hace falta que crees uno de nuevo, puedes si quieres usar ese. Si no, lo primero que tenemos que hacer es crear un superusuario para manejar el sitio de administración. Ejecuta el siguiente comando:

(venv) $ python manage.py createsuperuser

Se te pedirá que introduzcas un nombre para el usuario, un email (opcional) y una contraseñas. Si todo ha ido bien Django te dirá que el usuario se ha creado con exito y habremos creado un usuario administrador con los máximos privilegios de acceso al sitio.

El sitio de Administración de Django.

Inicia el servidor de desarrollo con el siguiente comando:

  (venv)$ python manage.py runserver
Abre la dirección http://127.0.0.1:8000/admin/ en tu navegador. Deberías ver el login de la página de administración de Django tal como se muestra en el siguiente imagen:

The Django administration site login screen


Logeate usando las credenciales del superusuario que creaste en el paso anterior. Veras el panel de administración de Django. 

panel de administración de django




Fue bastante sencillo ¿No crees?  Cuando registras un modelo en el panel de administración de Django, obtendrás una interfaz amigable desde la que gestionar los modelos lo que te permitirá listar, editar, crear y borrar objetos de una manera muy sencilla. 

Prueba a introducir algunos post y categorías. 

Primero añade algunas categorías o etiquetas para las post.

Luego puedes hacer click en 'Post' y arriba a la derecha de la pantalla encontrarás "añadir Post +" 

Prueba a crear y eliminar post para familiarizarte con el panel de administración.

Como usuario de momento, solo aparecerá el superusuario ya que es el que ha tenido acceso a la sesión.


Personalizando como se muestran los modelos.

Ahora echemos un vistazo a como personalizar el panel de administración. 

Edita el archivo admin.py de la aplicación Blog y cámbialo tal como se muestra. Las nuevas lineas aparecen sombreadas en amarillo:

PracticaDjango/Blog/templates/Blog/admin.py

from django.contrib import admin

# Register your models here.
# Importar del modelo tanto la categoría como el post
from .models import Categoria, Post

class Categoria_Admin(admin.ModelAdmin):
    # Campos de solo lectura en el panel de administración.
    readonly_fields = ('created', 'updated')

@admin.register(Post)
class Post_Admin(admin.ModelAdmin):
    # Campos de solo lectura en el panel de administración.
    readonly_fields = ('created', 'updated')
    list_display = ['titulo', 'autor', 'created', 'updated']

# Registramos todo lo anterior.
admin.site.register(Categoria, Categoria_Admin)
Hemos reformulado un poco el código para ver como hacer lo mismo usando un decorador pero si quieres puedes hacerlo igualmente como lo hicimos antes. 

Como novedad hemos introducido el atributo list_display para decirle a Django que campos del modelo queremos que nos muestre en el panel de administración cuando liste los registros.

Personalicémoslo con algunas opciones más.

Edita el archivo admin.py de la aplicación Blog y cámbialo tal como se muestra. Las nuevas líneas aparecen sombreadas en amarillo:

PracticaDjango/Blog/templates/Blog/admin.py 

from django.contrib import admin

# Register your models here.
# Importar del modelo tanto la categoría como el post
from .models import Categoria, Post

class Categoria_Admin(admin.ModelAdmin):
    # Campos de solo lectura en el panel de administración.
    readonly_fields = ('created', 'updated')

@admin.register(Post)
class Post_Admin(admin.ModelAdmin):
    # Campos de solo lectura en el panel de administración.
    readonly_fields = ('created', 'updated')
    list_display = ['titulo', 'autor', 'created', 'updated']
    list_filter = ['status', 'created', 'publish', 'author']
    search_fields = ['titulo', 'contenido']
    prepopulated_fields = {'slug': ('titulo',)}
    raw_id_fields = ['autor']
    date_hierarchy = 'updated'
    ordering = ['updated', 'created']

# Registramos todo lo anterior.
admin.site.register(Categoria, Categoria_Admin)

Vuelve al navegador y recarga la página que muestra los post en el panel de administración. Tendrá un aspecto parecido a esto:

Sitio de administración de Django personalizado



Puedes ver que los campos que se muestran en la página de los post son los que hemos especificado en la propiedad list_display. Esta página ahora incluye una barra lateral en la parte derecha que te permite filtrar los resultados por los campos incluidos el la propiedad list_filter.

Una barra de búsqueda ha aparecido también en la parte superior de la página. Esto es porque hemos definido una lista de campos en los que realizar búsquedas en la propiedad search_fields. En este caso podremos buscar cualquier cosa que aparezca en los campos 'título' y 'contenido'. Justo debajo de la barra de búsqueda, hay unos enlaces de navegación que nos permitirán movernos rápidamente a través de las fechas. Estos han sido definidos a partir del campo que aparece en la propiedad date_hierarchy. Se puede aplicar a cualquier campo del modelo que sea un DateField o DateTimeField. Tambien hemos especificado un criterio de ordenación por defecto de los registros usando los campo 'updated' y 'created' usando la propiedad 'ordering'. Recuerda que en el modelo habíamos especificado que se mostraran los registros usando estos mismos campos. Pero aquí hay una diferencia sutil pues aunque los campos de ordenación son los mismos, sin embargo se muestran primero los mas antiguos y luego los más modernos.

Ahora intenta añadir un nuevo post. También aquí verás algunos cambios. Cuando teclees el título de un nuevo post, el campo 'slug' se rellenará automáticamente. Le has dicho a Django que rellene este campo usando lo que se haya tecleado en el campo título.  Esto se lo hemos dicho usando el atributo prepopulated_fields.

Además, el campo autor ahora se muestra con una lupa en un lateral. Esto puede ser más útil que seleccionar un elemento de un desplegable cuando contiene una multitud de datos. Esto se consigue con el atributo raw_id_fields.

Ya ves como con unas pocas líneas de código hemos personalizado completamente el panel de administración. Puedes encontrar más opciones e información sobre el panel de Administración de Django en el siguiente enlace https://docs.djangoproject.com/en/4.2/ref/contrib/admin/.

- configurar las vistas

Una vista de Django es solamente una función de Python que recibe una solicitud web y devuelve una respuesta web. Toda la lógica para devolver la respuesta que queramos va dentro de la vista.

Primero, crearemos las vistas de la aplicación, luego definiremos un patrón URL para cada vista y
finalmente, crearemos las plantillas HTML para representar los datos generados por las vistas. Cada vista renderiza una plantilla, le pasará variables y devolverá una respuesta HTTP con la salida renderizada.

Como hemos creado una aplicación propia para el blog, tenemos que borrar las vista que existía en la aplicación Proyecto_web_app, con lo que el archivo de vistas de esta aplicación tendría un aspecto parecido a este:

PracticaDjango/Proyecto_web_app/views.py

from django.shortcuts import render, HttpResponse

# Create your views here.

def home(request):
    return render(request, 'Proyecto_web_app/inicio.html')

def tienda(request):
    return render(request, 'Proyecto_web_app/tienda.html')

# Esto lo borramos o comentamos.
# def blog(request):
#     return render(request, 'Proyecto_web_app/blog.html')

def contacto(request):
    return render(request, 'Proyecto_web_app/contacto.html')


Una vez hecho lo anterior, empecemos creando una vista que nos devuelva la lista de los post que existan.

 Edita el archivo views.py de la aplicación Blog e introduce este código.

PracticaDjango/Blog/views.py: 

from django.shortcuts import render
from .models import Post

# Create your views here.
def lista_post(request):
    posts=Post.objects.all()
    return render(request, 'Blog/lista.html', {'posts': posts})
La vista 'lista_post' toma un único parámetro que es 'request'. Este parámetro será necesario en todas las vistas.

En la vista, recuperamos todos los post existentes y finalmente, usamos el atajo 'render' que nos proporciona Django para renderizar la lista de los post con la plantilla que luego definiremos en ''Blog/blog.html". Es decir, esta función toma el objeto de la solicitud, la ruta de la plantilla y las variables de contexto para representar la plantilla dada. Devolverá un objeto "HttpResponse" con el texto representado que normalmente será HTML. El atajo 'render' va a tener en cuenta la variable del contexto (el request), por lo que cualquier dato o variable que haya en el puede ser usado en la plantilla.

Creemos una segunda vista también, para mostrar cada uno de los post de forma individual. Añade la siguiente función al código de views.py que hemos editado anteriormente.

PracticaDjango/Blog/templates/Blog/views.py

from django.http import Http404

def detalle_post(request, id):
    try:
        post = Post.objects.get(id=id)
    except Post.DoesNotExist:
        raise Http404("Post no encontrado.")
    return render(request, 'Blog/detalle.html', {'post': post})
Esta es la vista para el detalle de un determinado post. Esta vista toma el argumento id de una publicación. Intentamos buscar el post que se corresponde con el id dado. Se generará una excepción Http404 para devolver un error HTTP 404 si se genera la excepción del modelo DoesNotExist, porque no se encuentra ningún resultado.

Finalmente, usamos el atajo render() para renderizar la publicación recuperada usando una plantilla.

Sin embargo Django nos facilita un atajo para esto mismo usando muchas menos líneas de código. Es decir usando el código siguiente se buscará el post que se corresponda con el id dado y si no existe se renderizará un error HTTP 404. (el host ha sido capaz de comunicarse con el servidor, pero no existe el recurso que ha sido pedido.)

Atajo get_object_or_404 

PracticaDjango/Blog/templates/Blog/views.py

from django.shortcuts import render, get_object_or_404

# ...

def detalle_post(request, id):
    post = get_object_or_404(Post, id=id)
    return render(request, 'Blog/detalle.html', {'post': post})


Añadiendo las URL´s para las vistas.


Los patrones de URL's nos permiten asignar URL a las vistas. Se componen de un string, una vista y opcionalmente de un nombre que nos permita referirnos a esa URL en todo el proyecto. Django recorre los patrones de URL´s hasta que encuentra el primero que coincide con la URL solicitada. Luego Djando importa la vista asocida y lo ejecuta.

Luego crearemos la plantilla blog.html que renderizará el programa.

Al igual que hicimos para las vistas, como antes las URL para el blog se encontraban dentro de la aplicación "Proyecto_web_app" tenemos que editar este archivo y borrar la parte que se refiere a los datos del blog.

PracticaDjango/Proyecto_web_app/urls.py

from django.urls import path
from . import views

app_name  = 'Proyecto_web_app'

urlpatterns = [
    path('', views.home, name='home'),
    path('tienda/', views.tienda, name='tienda'),
    # borramos o comentamos la parte que hace referencia al blog
    # porque ahora está en otra aplicación.
    # path('blog/', views.blog, name='blog'),
    path('contacto/', views.contacto, name='contacto'),

]

Después de lo anterior, crea un archivo urls.py en el directorio de la aplicación Blog y añade las siguientes líneas.


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('<int:id>/', views.detalle_post, name='detalle_post'),
]

Lo siguiente que tenemos que hacer es añadir los patrones URLs de la aplicación Blog, en los patrones URL´s principales del proyecto. 

Edita el archivo urls.py del proyecto y añade el código que aparece resaltado.

PracticaDjango/PracticaDjango/urls.py

from django.contrib import admin
from django.urls import path, include

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('Proyecto_web_app.urls', namespace='Proyecto_web_app')),
    path('servicios/', include('Servicios.urls', namespace='Servicios')),
    path('blog/', include('Blog.urls', namespace='Blog')),
]
urlpatterns+=static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

- Creando la plantilla para las vistas.

Hemos creado las vistas y los patrones URL para la aplicación Blog. Los patrones de URL asignan URL a las vistas, y las vistas deciden que datos se devuelven al usuario. Las plantillas definen como se muestran esos datos. Normalmente es código HTML junto con el lenguaje de plantillas de Django. 

Puedes encontrar más información sobre el lenguaje de plantilla de Django en https://docs.djangoproject.com/en/4.2/ref/templates/language/

Crea los siguientes directorios dentro de la aplicación blog.

PracticaDjango/Blog/

templates/
        Blog/            
  

Una vez creada la carpeta movemos el archivo blog.html del template del proyecto al directorio que hemos creado anteriormente (template/blog/) y como hemos usaremos más vistas lo renombramos a lista.html

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="{% url 'Blog:detalle_post' post.id %}">{{post.titulo}}</a>
        </h2>
        <p class="date">Publicado {{ post.autor }} por {{ post.updated }} </p>
        {{ post.contenido|truncatewords:30|linebreaks }}
    {% endfor %}    
</div>
{% endblock %}

Con la etiqueta de plantilla {% extends %}, le decimos a Django que herede de la plantilla Proyecto_web_app/base.html.

Luego, llenamos los bloques de título y contenido de la plantilla base con el contenido que hemos definido en los bloques 'title' y 'content'. Posteriormente iteramos a través de las publicaciones y mostramos  su título, fecha de publicación, autor y contenido, incluido un enlace en el título a la URL detallada del post. Construimos la URL usando la etiqueta de plantilla {% url %} proporcionada por Django.

Esta etiqueta de plantilla te permite crear URL dinámicamente por su nombre. Usamos Blog:detalle_post para que consulte la URL de detalle_post en el espacio de nombres del Blog. Pasamos el parámetro post.id que se necesita para construir la URL para cada publicación o post.

En el contenido de la publicación, aplicamos dos filtros de plantilla: 

- truncatewords: trunca el valor al número de palabras especificadas 

y

- linebreaks: los saltos de línea se convierten en saltos de línea HTML.


Creando la plantilla para ver un post individual.

Crea y edita el archivo detalle.html en el mismo directorio que la plantilla anterior. Introduce el siguiente código.

PracticaDjango/Blog/templates/Blog/detalle.html

{% extends "Proyecto_web_app/base.html" %}

{% block title %}{{ post.title }}{% 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|linebreaks }}

    <!-- 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>
</div>

{% endblock %}
Para que la barra de navegación funcione correctamente tenemos que asignar cada enlace a su url de la aplicación correspondiente. Edita el archivo nav.html que esta en la aplicación Proyecto_web_app y déjalo de la siguiente forma.

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/nav.html

<ul class="nav justify-content-center my-2">

    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:home' as home_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == home_url %}
        text-black{% endif %}" href="{{ home_url }}">Inicio</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Servicios:servicios' as servicios_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == servicios_url %}
        text-black{% endif %}" href="{{ servicios_url }}">Servicios</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Blog:lista_post' as blog_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == blog_url %}
        text-black{% endif %}" href="{{ blog_url }}">Blog</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Tienda:tienda' as tienda_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == tienda_url %}
        text-black{% endif %}" href="{{ tienda_url }}">Tienda</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Contacto:contacto' as contacto_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == contacto_url %}
        text-black{% endif %}" href="{{ contacto_url }}">Contacto</a>
    </li>
</ul>


Para finalizar tendremos que ir a la consola de administración de Django y crear algunos post del blog de prueba. Luego si ejecutamos el servidor quedaría algo así:

lista de post publicados

y entramos en uno de ellos para ver que todo funciona correctamente:

detalle de un post del blog


Sin embargo, cuando ahora entramos en un post determinado, en la barra de navegación la palabra 'Blog' no aparece resaltada. Esto es debido a que en el código de la barra de navegación, solamente se resalta la palabra si su url es igual a '/blog/'. Al entrar en un post específico, por ejemplo el que tiene una id igual a '4', su url sería igual a /blog/4/ con lo que no se cumpliría la condición al ser distinto. Esto lo solucionamos fácilmente usando la etiqueta |slice que nos proporciona Django para recortar la url que nos proporciona request.path al consultar un post específico y dejarlo solamente en '/blog/' independientemente de todo lo que venga detrás. Para ello como '/blog/' son seis caracteres usamos la etiqueta |slice: ':6' en el código de la barra de navegación de la siguiente forma:

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/nav.html

...
<li class="nav-item px-lg-4">
     {% url 'Blog:lista_post' as blog_url %}
     <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path|slice:':6' == blog_url %}
     text-black{% endif %}" href="{{ blog_url }}">Blog</a>
</li>
...
y ahora ya aparece en negro y resaltado el enlace aunque estemos en un post determinado y no en la lista de ellos.

enlace post individual resaltado



Estaría bien poder buscar los diferentes post por categorías de búsqueda. Es decir que en la parte inferior de la página nos apareciesen unos enlaces a las diferentes categorías, de forma que al pulsar en ellas se mostrasen solo los post que pertenecen a esas categorías. Por ejemplo:


mostrar los post en función de las categorías

Lo primero que tenemos que hacer es añadir los enlaces, tal como muestra la imagen superior al final de la página. Lo hacemos añadiendo al final del archivo de la plantilla el siguiente código, justo antes del {% endblock %} final:

PracticaDjango/Blog/templates/Blog/blog.html

....

<section>
    <div style="width: 75%; margin: auto; color: white; text-align: center;">
        Categorías:
        {% for categoria in categorias %}
        <a href="{% url 'Blog:detalle_post' categoria.id %}" class="link-info">
            <span style="color:rgb(2, 7, 59)">{{ categoria.nombre }}</span>
        </a>&nbsp;&nbsp;&nbsp;
        {% endfor %}
    </div>
</section> 

</div>

{% endblock %}


A la plantilla le hemos pasado las diferentes categorías que pueden tener los post publicados. Esto lo conseguimos modificando la vista en el archivo views.py.

PracticaDjango/Blog/views.py

from django.shortcuts import render, get_object_or_404, get_list_or_404

from . models import Post,Categoria

from django.http import Http404

# Create your views here.

def lista_post(request):
    posts = get_list_or_404(Post)
    categorias = get_list_or_404(Categoria)
    return render(request, 'Blog/lista.html', {'posts': posts, 'categorias': categorias})
...


Para que cuando pulsemos los enlaces de las categorías se nos muestren los post que tienen esa categoría y no todos como hasta ahora, tenemos que registrar una nueva ruta:


PracticaDjango/Blog/urls.py

from django.urls import path
# load views of these applications.
from . import views

urlpatterns = [
    path('', views.blog, name='Blog'),
    path('categoria/<int:categoria_id>/', views.categoria, name='categoria'),
]

Esta URL será obligatorio pasarle un argumento que es la categoría cuyos post nos interesa mostrar.

Luego añadimos al archivo de vistas la nueva función 'categoría' que se ejecutará cuando se acceda a la ruta anteriormente creada pasándole el id de la categoría como parámetro:

PracticaDjango/Blog/views.py

from django.shortcuts import render, get_object_or_404, get_list_or_404

from . models import Post,Categoria

from django.http import Http404

# Create your views here.

def lista_post(request):
    posts = get_list_or_404(Post)
    categorias = get_list_or_404(Categoria)
    return render(request, 'Blog/lista.html', {'posts': posts, 'categorias': categorias})

def detalle_post(request, id):
    """Muestra todos los post"""
    post = get_object_or_404(Post, id=id)
    return render(request, 'Blog/detalle.html', {'post': post})

def categoria(request, categoria_id):
    """Muesta una lista de post en base a su categoría"""
    categoria=Categoria.objects.get(id=categoria_id)
    posts=Post.objects.filter(categorias=categoria_id)
    return render(request, 'Blog/categoria.html', {'categoria': categoria, 'posts': posts})

Lo que hace esta función es mirar a que categoría pertenece es id que le hemos pasado como parámetro, a continuación filtra todos los post que contienen esa categoría y vuelve a renderizar la pagina que muestra los blogs, pero solamente con los post que contienen esa categoría y no con todos. Ese archivo que se renderizará será casi idéntico a blog.html. Crea una nueva plantilla llamada categoria.html y agrégale este código.

PracticaDjango/Blog/templates/Blog/categoria.html

<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}

<!-- Establecemos el titulo de la página -->
{% block title %}Post por Categorias{% endblock %}

{% 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="{% url 'Blog:detalle_post' post.id %}">{{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 -->
<div class="d-flex justify-content-center align-items-center">
    <a href="{% url 'Blog:lista_post' %}" class="btn btn-primary">Regresar</a>
</div>
{% endblock %}


Código de este capítulo en GitHub

jueves, 4 de mayo de 2023

15.- Reubicación de todos los archivos de una aplicación dentro de la misma. Aplicación SERVICIOS.

En capítulos anteriores habíamos creado una aplicación nueva llamada servicios y en este tema, vamos a ubicar sus elementos correctamente. Esto es porque, por ejemplo, el "template" de servicios lo tenemos ubicado dentro de la aplicación Proyecto_web_app y esto no tiene mucho sentido, ya que el objetivo de crear una aplicación es poder utilizarla en el futuro y reescalarla. Si tenemos los archivos de esta aplicación en diferentes sitios esto no se puede hacer.

Por ello, vamos a empezar creando una nueva carpeta dentro del "Servicio" donde ubicar los templates. Lo suyo sería crear dentro de la aplicación "Servicios" una nueva llamada, lógicamente, "templates". Dentro de esta carpeta también conviene tener subcarpetas por si queremos más adelante  insertar más templates. Así en el directorio del Servicio creamos una nueva carpeta "templates", un subdirectorio llamado "Servicios" y movemos dentro el archivo "servicios.html" desde la carpeta en donde lo teníamos a esta nueva que hemos creado.

Se vería de la siguiente forma:


Al hacer esto acabamos de destrozar todas las URLs que apuntan a esta plantilla o template. Si vamos al navegador e intentamos entrar en la página de servicios no funcionaria. Para arreglarlo tenemos que hacer varias cosas. Empezaremos arreglando las vistas. Dentro de "Proyecto_web_app/views.py" es donde teníamos configuradas las vistas. Por tanto debemos llevarnos el código que define la vista de este template al views de la aplicación "Servicios" así como la línea de importación del modelo. Así que cortamos y pegamos y dejamos el archivos de vista de "Servicios" de la siguiente forma:

PracticaDjango/Servicios/views.py

from Servicios.models import Servicio
from django.shortcuts import render

# Create your views here.
def servicios(request):
    datos = Servicio.objects.all()
    # Anteriormente era:
    # return render(request, 'Proyecto_web_app/servicios.html', {'servicios': datos})
    # Pero al moverlo hay que cambiar el directorio que contiene a la plantilla.
return render(request, 'Servicios/servicios.html', {'servicios': servicios})

Además tenemos que modificar el directorio donde se encuentra la plantilla de servicios que ahora esta en el directorio template de la aplicación 'Servicios' y aprovechamos también para cambiar el nombre del contexto que le pasamos a la plantilla para que sea más coherente.


Siguiente Paso. Debemos ir al archivo URLs de la aplicación "Proyecto_web_app". Si recordáis en este archivo tenemos la url apuntando al  path de servicios. Pero ahora esto ya no apunta aquí porque al cortar lo hemos quitado. Lo que debemos hacer es crear dentro de la App "Servicios" nuestro archivo "urls.py" y dentro de el ubicar este "path". 

Para situarnos un poco más. Este es el estado actual del archivo de vistas de la aplicación "Proyecto_web_app"


PracticaDjango/Proyecto_web_app/urls.py: 

from django.urls import path
# load views of these applications.
from Proyecto_web_app import views

urlpatterns = [
    path('', views.home, name='Home'),
    path('servicios/', views.servicios, name='Servicios'),
    path('tienda/', views.tienda, name='Tienda'),
    path('blog/', views.blog, name='Blog'),
    path('contacto/', views.contacto, name='Contacto'),
]

Lo que está en amarillo tenemos que borrarlo porque lo vamos a desplazar a la nueva ubicación.

Creamos dentro de la aplicación "Servicios" el archivo "urls.py" y lo dejamos de la siguiente forma:

PracticaDjango/Servicios/urls.py: 

from django.urls import path
# load views of these applications.
from . import views

app_name = 'Servicios'

urlpatterns = [
    path('', views.servicios, name='Servicios'),
]

El usar "from . import views' es porque estamos usando las views.py de los servicios y no de la aplicación principal. 

Ahora tenemos que ir al urls de la aplicación principal y registrar esta aplicación. Ahora mismo tenemos registrado lo siguiente:

PracticaDjango/PracticaDjango/urls.py: 

from django.contrib import admin
from django.urls import path, include
# Para registrar los archivos de las imagenes y poder verlas
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('Proyecto_web_app.urls')),
]
urlpatterns+=static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Pero tenemos que registrar también la app Servicios para que se pueda ir a esa URL. El procedimiento es el mismo que con las otras rutas:

PracticaDjango/PracticaDjango/urls.py: 

PracticaDjango/PracticaDjango/urls.py: 

from django.contrib import admin
from django.urls import path, include
# Para registrar los archivos de las imagenes y poder verlas
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('servicios/', include('Servicios.urls')),
    path('', include('Proyecto_web_app.urls')),
]
urlpatterns+=static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Nos queda arreglar algunas cosas en la plantilla.

La primera de ellas es que la barra de navegación apunta a un enlace que ya no existe por lo que debe apuntar a su nueva ubicación, lo que es muy fácil de cambiar.

Proyecto_web_app/templates/Proyecto_web_app/nav.html: 

...
    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:home' as home_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == home_url %}
        text-black{% endif %}" href="{{ home_url }}">Inicio</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Servicios:servicios' as servicios_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == servicios_url %}
        text-black{% endif %}" href="{{ servicios_url }}">Servicios</a>
    </li>

    <li class="nav-item px-lg-4">
        {% url 'Proyecto_web_app:blog' as blog_url %}
        <a class="nav-link text-uppercase fw-bold text-expanded {% if request.path == blog_url %}
        text-black{% endif %}" href="{{ blog_url }}">Blog</a>
    </li>
...

y como hemos cambiado el contexto que enviamos a la plantilla hay que cambiar donde se pusimos 'datos' por 'servicios' que es como se llama el contexto que le pasamos:

Servicios/templates/Servicios/servicios.html

<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}

<!-- Establecemos el titulo de la página -->
{% block title %}Servicios{% endblock %}

<!-- Definimos su contenido -->
{% block content %}

<h2>Esta es la página de Servicios.</h2>
<!--Para recorrer los servicios que vayamos creando-->
{% for servicio in servicios %}
<div>
    <p>
    <h2>{{servicio.titulo}}</h2>
    <p>{{servicio.contenido}}</p>
    <p>
        <img src="{{servicio.imagen.url}}">
    </p>
    </p>
</div>
{% endfor %}

{% endblock %}


Código de este capítulo en GitHub