Tenemos que crear una página con un formulario para que los usuarios nos envíen sus comentarios.
Django viene con dos clases que nos permitirán construir de forma sencilla formularios:
- Form: nos permite construir formularios estandar definiendo los campos y las validaciones.
- ModelForm: te permitirá construir formularios usando directamente el modelo. Proporciona todas las funcionalidades de la clase anterior, pero los campos del formulario se pueden declarar explícitamente o se pueden generar automáticamente desde los campos del modelo. El formulario se puede utilizar para crear o editar instancias del modelo.
Tendrá una apariencia similar a esto:
- Empezamos creando la APP. En consola utilizamos el comando:
$ python manage.py startapp Contacto
- Luego registramos la nueva aplicación. (aquí lo hacemos de otra forma diferente a como lo hemos hecho con las otras aplicaciones, usando Contacto.apps.ContactoConfig)
PracticaDjango/PracticaDjango/settings.py
...
INSTALLED_APPS = [
# my applications
'Proyecto_web_app',
'Servicios',
'Blog',
'Contacto.apps.ContactoConfig',
# 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 = [
...
- Posteriormente tenemos que decirle a Django que cuando se entre en la url /contacto se ejecute una determinada vista que será la encargada de renderizar el formulario de contacto. Para ello definiremos la ruta url de la aplicación, primeramente en el archivo urls.py del proyecto.
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('blog/', include('Blog.urls')),
path('contacto/', include('Contacto.urls')),
path('', include('Proyecto_web_app.urls')),
]
urlpatterns+=static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Es decir cuando se vaya a la URL /contacto/, Django debe buscar la ruta en el archivo urls.py pero de la aplicación "Contacto", no en la del proyecto.
Entra en el directorio de la aplicación Contacto y crea el archivo urls.py. Luego añade el siguiente código:
PracticaDjango/Contacto/urls.py:
from django.urls import path
# load views of these applications.
from . import views
app_name = 'Contacto'
urlpatterns = [
path('', views.contacto, name='contacto'),
]
2. `from . import views`: Esta línea importa las vistas (funciones) definidas en el archivo `views.py` del directorio actual (indicado por `.`). Las vistas son responsables de manejar las solicitudes HTTP y devolver una respuesta. Todavia no existe lo crearemos en breve.
3. `urlpatterns = [ ... ]`: Aquí comienza la lista de patrones de URL. La variable `urlpatterns` es una lista que contiene las rutas URL de la aplicación.
4. `path('', views.contacto, name='contacto')`: Esta línea define una ruta URL vacía (raíz) que se asigna a la vista `contacto` del archivo `views.py`. El primer argumento `''` representa la ruta URL (en este caso, la raíz del sitio web). El segundo argumento `views.contacto` especifica la vista que se debe llamar cuando se accede a esta URL. El tercer argumento `name='contacto'` es un nombre opcional para esta ruta URL, que se puede utilizar para referirse a ella en otras partes del código.
En resumen, este código define una única ruta URL vacía (`''`) que se asigna a la vista `contacto`. Cuando se accede a la raíz del sitio web, se llamará a la vista `contacto` para manejar la solicitud.
- Para manejar los templates de esta aplicación crearemos el directorio siguiente:
$ PracticaDjango/Contacto/templates/Contacto/
y dentro crearemos el archivo "contacto.html" que será el que contenga el código HTML del formulario de la aplicación que construiremos más adelante.
- Tenemos que editar el archivo de vistas "views.py" de la aplicación que será el que ejecute la función designada cuando se entre el la url de contacto.
PracticaDjango/Contacto/views.py:
from django.shortcuts import render
def contacto(request):
return render(request, "Contacto/contacto.html")
A) Creación del formulario con la clase Form.
PracticaDjango/Contacto/forms.py
from django import forms
class FormularioContacto(forms.Form):
# Especificamos los campos del formulario
nombre = forms.CharField(label="Nombre", max_length=50, required=True)
email = forms.EmailField(label="Email", max_length=50, required=True)
contenido = forms.CharField(label="Contenido", max_length=400, widget=forms.Textarea(attrs={'cols': 45, 'rows': 5}))
Nota: los formularios pueden estar en cualquier parte del código del proyecto. Sin embargo por convención se colocan dentro de cada aplicación en un archivo llamado forms.py.
De manera muy similar a cómo un modelo de Django describe la estructura lógica de un objeto, su comportamiento y la forma en que sus partes se nos presentan, una clase Form describe un formulario y determina cómo funciona y cómo aparece.
De forma similar a cómo los campos de una clase del archivo models.py se mapean a campos de base de datos, los campos de una clase de formulario se mapean a elementos <input> de formulario HTML. (Un ModelForm mapea los campos de una clase de modelo a elementos <input> de formulario HTML a través de un Form; esto es en lo que se basa el administrador de Django).
Los campos de un formulario son ellos mismos clases; administran los datos del formulario y realizan la validación cuando se envía el formulario.
Un campo de formulario se representa para un usuario en el navegador como un "widget" HTML. Cada tipo de campo tiene una clase de widget predeterminada apropiada, pero estas pueden ser reemplazadas según sea necesario.
En el formulario de contacto queremos que se nos facilite un nombre, un email y un comentario.
Este código de Django define un formulario de contacto utilizando la biblioteca de formularios de Django. Aquí hay una explicación línea por línea del código:
1. `from django import forms`: Importa el módulo `forms` de la biblioteca Django, que contiene las clases para crear formularios.
3. `class FormularioContacto(forms.Form):` Define una clase llamada `FormularioContacto` que hereda de la clase `forms.Form`. Esto significa que `FormularioContacto` es un formulario de Django.
5. `nombre = forms.CharField(label="Nombre", max_length=50, required=True)`: Define un campo llamado `nombre` en el formulario. Este campo es de tipo `CharField`, que representa un campo de texto. El argumento `label` establece la etiqueta que se mostrará para este campo en el formulario. `max_length` especifica la longitud máxima del campo de texto, y `required` indica que este campo es obligatorio. Aunque no haría falta ponerlo porque por defecto si no se especifica otra cosa es un campo obligatorio, si quieres que sea opcional habría que poner required = False
6. `email = forms.EmailField(label="Email", max_length=50, required=True)`: Define un campo llamado `email` en el formulario. Este campo es de tipo `EmailField`, que valida automáticamente que el valor ingresado sea una dirección de correo electrónico válida.
7. `contenido = forms.CharField(label="Contenido", max_length=400, widget=forms.Textarea(attrs={'cols': 45, 'rows': 5}))`: Define un campo llamado `contenido` en el formulario. Este campo también es de tipo `CharField`, pero se usa un widget `Textarea` para permitir la entrada de texto multilínea. El argumento `attrs` se utiliza para especificar los atributos adicionales del widget, en este caso, se establece el número de columnas (`cols`) y filas (`rows`) del área de texto.
Manejo de formularios en las vistas.
Ahora tenemos que llevar esto que acabamos de crear a la vista. Necesitamos una vista para crear una instancia del formulario y manejar el envío del mismo. Así que abrimos el views.py de la aplicación 'Contacto' y lo primero que vamos a hacer es importar el formulario y crear una instancia del mismo. Luego se lo pasamos al render como parámetro un tercer elemento con un diccionario con la nomenclatura de nombre: valor.
PracticaDjango/Contacto/views.py:
from django.shortcuts import render # API del formulario from .forms import FormularioContacto def contacto(request): formulario_contacto = FormularioContacto() return render(request, "Contacto/contacto.html", {'form':formulario_contacto})
Vamos a ir creando un template básico para trabajar con el, aunque luego lo modifiquemos:
PracticaDjango/Contacto/templates/Contacto/contacto.html:
<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}
<!-- Establecemos el titulo de la página -->
{% block title %}Contacto{% endblock %}
<!-- Definimos su contenido -->
{% block content %}
<h1 class="text-center">Contacta con nosotros.</h1>
# Muestra el formulario que le hemos pasado.
<div><p>{{form}}</p></div>
{% endblock %}
Antes de nada tenemos que desvincular el enlace /contacto de la aplicación Proyecto_web_app que hasta ahora era la encargada de renderizarlo. Para ello tenemos que:
- ir al archivo Proyecto_web_app/views.py y borrar la vista 'contacto'.
- ir al archivo Proyecto_web_app/urls.py y borrar el path correspondiente a la aplicación contacto.
- ir al directorio Proyecto_web_app/templates/ y borrar la plantila contacto.html.
Después de esto ya podemos ejecutar el servidor y ver que funciona. Abre el terminal y ejecuta:
(venv) $ python manage.py runserver
Desde el enlace CONTACTO o tecleando en el navegador http://127.0.0.1:8000/contacto/, comprueba que todo esta correcto.
Aunque los campos quedan muy feos, vemos que el formulario se renderiza correctamente (luego lo modificaremos para que quede más bonito y le añadiremos los botones):
Vamos a centrarnos en dar formato a este formulario. Si utilizamos Bootstrap para darle formato es muy sencillo. Ponemos el código y lo comento:
PracticaDjango/Contacto/templates/Contacto/contacto.html:
<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}
<!--Cargamos de nuevo el contenido estático pra usar la etiqueta bootstrap_form-->
{% load django_bootstrap5 %}
<!-- Establecemos el titulo de la página -->
{% block title %}Contacto{% endblock %}
<!-- Definimos su contenido -->
{% block content %}
<h1 class="text-center">Contacta con nosotros.</h1>
<!-- Muestra el formulario que le hemos pasado.-->
<form action="" method="POST">
{% csrf_token %}
<div class="container w-25 bg-primary rounded-1">
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Enviar</button>
<button type="reset" class="btn btn-secondary">Borrar</button>
</div>
</form>
{% endblock %}
Este código es una plantilla HTML que muestra el formulario web. Aquí hay una explicación línea por línea:
1. `<form action="" method="POST">`: Esto crea la estructura del formulario HTML. El atributo `action` determina la URL a la que se enviarán los datos del formulario cuando se envíe, en este caso, se deja vacío porque se va a enviar la información a la misma url en la que ya estamos. El atributo `method` especifica el método HTTP utilizado para enviar los datos, en este caso, `POST`.
Cuando acedemos a el formulario por primera vez, esta petición es de tipo GET.
Sin embargo para pasar la información del formulario vamos a utilizar el método POST. Cuando el usuario rellena el formulario y le da al botón enviar en realidad se está creando un diccionario con los datos introducidos que es la que se envía. Además cuando el usuario envíe la información debería salir un feed-back que comunicara el éxito o fracaso del envío. Esto lo codificaremos luego.
2. `{% csrf_token %}`: Esta es una etiqueta especial en el lenguaje de plantilla Django. Genera un campo de token de seguridad (CSRF) que protege el formulario contra ataques CSRF (Cross-Site Request Forgery). Django utiliza este token para verificar que las solicitudes POST provienen del mismo sitio web y no de una fuente maliciosa. Siempre tenemos que ponerla al crear el formulario.
3. `<div class="container w-25 bg-primary rounded-1">`: Esto crea un contenedor de estilo para el formulario. La clase `"container"` proporciona un diseño de contenedor en Bootstrap, mientras que las clases `"w-25"` definen el ancho del contenedor (25% del ancho del contenedor principal). Las clases `"bg-primary"` y `"rounded-1"` establecen el color de fondo azul y los bordes redondeados del contenedor respectivamente.
4. `{% bootstrap_form form %}`: Esta la etiqueta de Django que renderiza automáticamente los campos del formulario utilizando el paquete Django-Bootstrap. Renderiza el formulario `form` en el HTML generado. Es la responsable de crear el formulario tal como lo vemos. Puedes encontrar más información y opciones en https://django-bootstrap5.readthedocs.io/en/latest/templatetags.html
5. `<button type="submit" class="btn btn-primary">Enviar</button>`: Este es un botón de envío del formulario. Al hacer clic en él, se enviarán los datos del formulario. La clase `"btn btn-primary"` aplica estilos de Bootstrap al botón.
6. `<button type="reset" class="btn btn-secondary">Borrar</button>`: Este es un botón de reinicio del formulario. Al hacer clic en él, se restablecerán todos los campos del formulario a sus valores predeterminados. La clase `"btn btn-secondary"` aplica estilos de Bootstrap al botón.
En resumen, este código muestra un formulario web básico con campos generados automáticamente utilizando Django-Bootstrap. Los datos del formulario se enviarán mediante el método `POST` a una URL especificada en el atributo `action` del formulario. Además, se incluye un token de seguridad CSRF para proteger el formulario contra ataques maliciosos.
De esta forma tan simple nuestro formulario tendrá este bonito aspecto:
Si pruebas a intentar este formulario en blanco, tanto el propio navegador, como la libreria form de Django hacen una validación y te dicen que rellenes el campo que está en blanco, que cuando lo definimos dijimos que era obligatorio rellenarlo. También puedes probar a poner una dirección de correo electrónico que no sea válida y te dará también un error.
La mayoría de los navegadores modernos evitarán que envíes un formulario con campos vacíos o con errores. Esto se debe a que el navegador valida los campos en función de sus atributos antes de enviar el formulario. En este caso, el formulario no se enviará y el navegador mostrará un mensaje de error para los campos que estén incorrectos. Para probar la validación de formularios de Django utilizando un navegador moderno, puedes omitir la validación del formulario del navegador añadiendo el atributo "novalidate" al elemento HTML <form>, como por ejemplo <form method="post" novalidate>. Puedes agregar este atributo para evitar que el navegador valide los campos y probar tu propia validación de formularios. Después de que hayas terminado de probar, elimina el atributo "novalidate" para mantener la validación de formularios del navegador.
Puedes encontrar más información sobre cómo trabajar con formularios en https://docs.djangoproject.com/en/4.1/topics/forms/.
Ahora se trata de que la información que escriba el usuario se envíe. Cuando se rellenen los campos y se pulse en el botón "Enviar" la información se enviará a la URL /contacto/ usando el método Post. Esta información se envía a través de un diccionario. Para gestionarla tenemos que hacer lo siguiente en el archivo views.py:
PracticaDjango/Contacto/views.py:
from django.shortcuts import render # API del formulario from .forms import FormularioContacto def contacto(request): formulario_contacto = FormularioContacto() # Si se ha hecho "POST" rescata la información del diccionario enviado. if request.method=="POST": # El método post devuelve un diccionario con los datos del formulario formulario_contacto = FormularioContacto(data=request.POST) # Si el formulario de contacto es válido, se han rellenado los campos obligatorios # y los campos están bien definidos. if formulario_contacto.is_valid(): datos = formulario_contacto.cleaned_data nombre = datos.get("nombre") email = datos.get("email") contenido = datos.get("contenido") return render(request, "Contacto/contacto.html", {'form':formulario_contacto})
Si la el método que se recibe es "POST", y se comprueba que el formulario es correcto, se almacenan los campos del diccionario en sus respectivas variables "nombre", "email" y "contenido".
No obstante cuando el usuario pulse el botón enviar debería recibir un feedback, es decir un mensaje que le diga que la información se ha enviado correctamente. Para ello hay que tener en cuenta que cada vez que se pulsa el botón enviar, y se envía la información con el método POST, hay una recarga de página con la información introducida en el formulario. Lo que podemos hacer es a esa recarga enviarle un parámetro, una palabra por ejemplo. Y eso tendrá que estar dentro del "if".
Para pasar un parámetro a contacto.html usaremos una redirección.
PracticaDjango/Contacto/views.py:
from django.shortcuts import render # Para poder redireccionar a otras urls from django.shortcuts import redirect # API del formulario from .forms import FormularioContacto def contacto(request): formulario_contacto = FormularioContacto() # Si se ha hecho "POST" rescata la información del diccionario enviado. if request.method=="POST": # El método post devuelve un diccionario con los datos del formulario formulario_contacto = FormularioContacto(data=request.POST) # Si el formulario de contacto es válido, se han rellenado los campos obligatorios # y los campos están bien definidos. if formulario_contacto.is_valid(): datos = formulario_contacto.cleaned_data nombre = datos.get("nombre") email = datos.get("email") contenido = datos.get("contenido") return redirect("/contacto/?valido") return render(request, "Contacto/contacto.html", {'form':formulario_contacto})
En Django, se utiliza la función `redirect()` para redirigir al usuario a una URL específica. En este caso, la URL es `"/contacto/?valido"`.
El 'redirect()' es una función de utilidad que redirige al usuario a la URL especificada. Puede tomar como argumento una URL absoluta o una ruta relativa. En este caso, se proporciona una ruta relativa, que es "/contacto/?valido". El interrogante es como se pasa la información del parámetro cuando usamos el método GET. Después del ? podemos poner lo que queramos "valido", "ok" o algo similar.
"?valido" es el parámetro que se pasa a la URL. Generalmente los parámetros en una URL se utilizan para transmitir información adicional a la página de destino. En este caso, "?valido" indica que la página de contacto ha sido enviada exitosamente y se está utilizando como una señal para mostrar algún mensaje de éxito o realizar alguna otra acción en la página de destino.
Para que se muestre un mensaje de feedback tenemos que modificar la plantilla:
PracticaDjango/Contacto/templates/Contacto/contacto.html:
<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}
<!--Cargamos el contenido estático-->
{% load django_bootstrap5 %}
<!-- Establecemos el titulo de la página -->
{% block title %}Contacto{% endblock %}
<!-- Definimos su contenido -->
{% block content %}
<h1 class="text-center">Contacta con nosotros.</h1>
<!-- Para que si el formulario tuviese errores nos avise -->
{% if form.errors %}
<div class="alert alert-warning">
<strong>Warning!</strong> Por favor,revisa este campo..
</div>
{% endif %}
<!-- Si en una petición GET se envía el parámetro valido -->
{% if "valido" in request.GET %}
<div class="alert alert-success">
<strong>Success!</strong> Información Enviada Correctamente, Muchas Gracias!.
</div>
{% endif %}
<!-- Si en una petición GET se envía el parámetro novalido -->
{% if "novalido" in request.GET %}
<div class="alert alert-danger">
<strong>Danger!</strong> Ha habido un problema al enviar el correo, Vuelva a intentarlo!
</div>
{% endif %}
# Muestra el formulario que le hemos pasado.
<form action="" method="POST" class="form">
{% csrf_token %}
<div class="container w-25 bg-primary rounded-1">
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Enviar</button>
<button type="reset" class="btn btn-secondary">Borrar</button>
</div>
</form>
{% endblock %}
Por ejemplo, vamos a la pestaña de Contacto, introducimos un nombre, un email y un mensaje. Cuando pulsemos el botón enviar se produce un POST, se pasa la información a la vista, los datos se almacenan en nombre, email y contenido y después se produce una redirección por GET a la misma página pasándole a la url el parámetro "valido" y se tiene que mostrar, si todo va bien "Información Enviada Correctamente".
Envió de un email con los datos capturados al administrador del sistema.
Después modificamos el archivo views.py para que si el formulario es válido, acto seguido se envíe el correo y se emita un mensaje de confirmación:
PracticaDjango/Contacto/views.py:
from django.shortcuts import render # Para poder redireccionar a otras urls from django.shortcuts import redirect # API del formulario from .forms import FormularioContacto # Para enviar el correo electronico from django.core.mail import send_mail, EmailMessage from django.conf import settings # Para que el correo electrónico funcione en un hilo aparte. import threading def enviar_email_en_hilo(correo): '''Enviará el email en un hilo aparte para que no se bloquee el programa. ''' correo.send() def contacto(request): formulario_contacto = FormularioContacto() # Si se ha hecho "POST" rescata la información del diccionario enviado. if request.method=="POST": # El método post devuelve un diccionario con los datos del formulario formulario_contacto = FormularioContacto(data=request.POST) # Si el formulario de contacto es válido, se han rellenado los campos obligatorios # y los campos están bien definidos. if formulario_contacto.is_valid(): datos = formulario_contacto.cleaned_data nombre = datos.get("nombre") email = datos.get("email") contenido = datos.get("contenido") return redirect("/contacto/?valido") # Para enviar el correo electrónico email_from = settings.EMAIL_HOST_USER email_to = settings.EMAIL_DESTINATION # Esta sería una forma de enviarlo que ya vimos en el capitulo anterior. '''send_mail( f'Mensaje de {nombre}', f'{contenido} \nemail: {email}', email_from, [email_to], )''' # Para enviar el correo electrónico de otra forma correo = EmailMessage( 'Mensaje desde APP Django', f'El usuario {nombre} con la dirección {email} escribe lo siguiente:\n\n{contenido}', email_from, [email_to], reply_to=[email] # Para responder al correo del que nos escribe. ) try: # Enviar el correo en un hilo aparte thread = threading.Thread(target=enviar_email_en_hilo, args=(correo,)) thread.start() return redirect("/contacto/?valido") except: return redirect("/contacto/?novalido") # en get se pasan los parametros por la url usando ? return render(request, "Contacto/contacto.html", {'form':formulario_contacto})
Creación de Formularios usando la clase ModelForm.
- Un modelo "Comentario" para guardar los comentarios de los post.
- Un formulario que permita a los usuarios enviar comentarios y manejar la validación de datos.
- Una vista que procese el formulario y guarde un nuevo comentario en la base de datos.
- Una lista de comentarios y un formulario para añadir un nuevo comentario que pueda ser incluido en la plantilla "detalle.html" que es la que muestra un post en concreto.
Creando un modelo para los comentarios.
PracticaDjango/Blog/models.py
# ...
class Comentario(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comentarios')
autor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='post_comentarios')
email = models.EmailField()
cuerpo = models.TextField(max_length=400)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
activo = models.BooleanField(default=True)
class Meta:
ordering = ['created']
indexes = [models.Index(fields=['created']),]
def __str__(self):
return f"Comentario de {self.autor} en {self.post}"
(env) $ python manage.py makemigrations Blog
(env) $ python manage.py migrate
Añadiendo los comentarios al panel de administración.
PracticaDjango/Blog/admin.py
# ...
# Importar del modelo tanto la categoría como el post
from .models import Categoria, Post, Comentario
# ...
@admin.register(Comentario)
class Comentario_admin(admin.ModelAdmin):
list_display = ['autor', 'email', 'post', 'created', 'activo']
list_filter = ['activo', 'created', 'updated']
search_fields = ['autor', 'email', 'cuerpo']
Creando el formulario desde el modelo.
PracticaDjango/Blog/forms.py
from django import forms
from .models import Comentario
class ComentarioForm(forms.ModelForm):
class Meta:
model = Comentario
fields = ['autor', 'email', 'cuerpo']
Manejando los formularios basados en modelos en las vistas. (ModelForm)
PracticaDjango/Blog/views.py
from django.shortcuts import render, get_object_or_404, get_list_or_404 from .models import Post, Categoria, Comentario # from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.views.generic import ListView # Importamos el formulario para los comentarios de cada post. from .forms import ComentarioForm from django.views.decorators.http import require_POST # Create your views here. # .... @require_POST def post_comentario(request, post_id): post = get_object_or_404(Post, id=post_id) comentario = None # Se ha publicado un comentario form = ComentarioForm(data=request.POST) if form.is_valid: # creamos un objeto 'comentario' pero sin guardarlo en la base de datos. comentario = form.save(commit=False) # asignamos el post al comentario comentario.post = post comentario.save() return render( request, "Blog/comentario.html", {"post": post, "form": form, "comentario": comentario}, )
- Hemos obtenido el post al que añadir el comentario por su id utilizando el atajo get_object_or_404.
- Definimos la variable comentario con el valor inicial de None. Esta variable se usará para guardar el objeto comentario cuando se cree.
- Instanciamos el formulario con los datos enviados en el request a través del método POST y lo validamos usando el método is_valid(). Si el formulario no es válido se renderizará con los errores de validación.
- Si el formulario es válido se creara un nuevo objeto "comentario" llamando al método "save" del formulario y lo asignamos a una nueva variable "comentario" del siguiente modo. comentario = form.save(commit=False)
- El método save() crea una instancia del modelo al que el formulario está vinculado y lo guarda en la base de datos. Si lo llamas utilizando commit=False, se crea la instancia del modelo, pero no se guarda en la base de datos. Esto nos permite modificar el objeto antes de guardarlo definitivamente. IMPORTANTE: el metodo save() solo está disponible cuando utilizamos ModelForm pero no para las instancias creadas con Form ya que estas no están vinculadas a ningún modelo.
- Asignamos el post al comentario que hemos creado con "comentario.post = post"
- Grabamos el nuevo comentario en la base de datos llamando al método save().
- Finalmente renderizamos la plantilla "Blog/comentario.html", pasándole en el contexto el post, el formulario y los comentarios.
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'),
path('<int:post_id>/comentario/', views.post_comentario, name='post_comentario'),
]
Creando las plantillas para el formulario de comentario.
PracticaDjango/Blog/templates/Blog/form_comentario.html
<h2 style="margin-bottom: 4px;">Añade un nuevo comentario</h2>
<form action="{% url 'Blog:post_comentario' post.id %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><input type="submit" value="Añade comentario"></p>
</form>
En este mismo directorio creamos la plantilla "comentario.html" y añadimos el siguiente código.
PracticaDjango/Blog/templates/Blog/comentario.html
{% extends "Proyecto_web_app/base.html" %}
{% block title %}Añade un comentario{% endblock %}
{% block content %}
{% if comentario %}
<div style="background-color: beige;">
<h2>Tu comentario ha sido añadido</h2>
<p><a href="{{post.get_absolute_url}}">Vuelve al Post</a></p>
</div>
{% else %}
{% include "Blog/form_comentario.html" %}
{% endif %}
{% endblock%}
Esta es la plantilla para la vista "post_comentario". En esta vista esperamos que el formulario sea enviado usando el método Post. La plantilla contempla dos posibles escenarios:
- Si los datos enviados por el formularios son validos, es decir el formulario existe y no está vacío, la variable "comentario" contendrá el objeto comentario que fue creado, y se mostrará un mensaje de que el comentario ha sido añadido correctamente.
- Por el contrario, si los datos enviados por el formulario no son correctos, la variable "comentario" será igual a None. En este caso se mostrará el formulario para introducir el comentario. Hemos usado la etiqueta {% include %} para incluir la plantilla "form_comentario.html" que creamos anteriormente.
Añadiendo comentarios a la vista "post_detalle".
Edita el archivo views.py y modifica el código de la vista "detalle_post" de la siguiente forma:
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,
)
# Lista de comentarios activos para este post.
comentarios = post.comentarios.filter(activo=True)
# Formulario para que los usuarios comenten los post.
form = ComentarioForm()
return render(
request,
"Blog/detalle.html",
{"post": post, "comentarios": comentarios, "form": form},
)
#...
Revisemos el código que hemos añadido a la vista:
Hemos añadido una consulta a la base de datos para obtener todos los comentarios activos del post, de la siguiente forma:
comentarios = post.comentarios.filter(activo=True)
Esta búsqueda utiliza el objeto post. En vez de construir la busqueda desde el modelo "Comentario" directamente, aprovechamos el objeto post, para recuperar los comentarios. (related_name). También hemos creado una instancia del formulario de comentario con form = ComentarioForm().
Añadiendo comentarios a la plantilla de la vista detalle_post (detalle.html).
Necesitamos editar la plantilla Blog/detalle.html para añadir lo siguiente:
- Mostrar el número total de comentarios que tiene un determinado post.
- Mostrar una lista con los comentarios.
- Mostrar el formulario para que los usuarios añadan un nuevo comentario.
PracticaDjango/Blog/templates/Blog/detalle.html
{% extends "Proyecto_web_app/base.html" %} {% 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|linebreaks }} {% with comentarios.count as total_comentarios %} <h2> {{ total_comentarios }} comentario{{ total_comentarios|pluralize }} </h2> {% endwith %} <!-- 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 %}
Ahora, añadiremos la lista de comentarios activos para un post.
Edita de nuevo la plantilla y añade los siguiente cambios:
PracticaDjango/Blog/templates/Blog/detalle.html
{% extends "Proyecto_web_app/base.html" %} {% 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|linebreaks }} {% 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> </div> {% endblock %}
Hemos añadido la etiqueta {% for %} para iterar a través de los comentarios. Si la lista de comentarios esta vacía mostraremos al usuario un mensaje informándole de que aun no hay comentarios para ese post, usando la etiqueta {% empty %}. También enumeramos los comentarios usando la variable {% forloop.counter }} que nos dice cual es la iteración en el bucle, es decir 1 , 2 , etc. Para cada post mostramos también la fecha de creación, quien es el autor y el contenido o cuerpo del mensaje.
Finalmente añadimos el formulario a la plantilla.
PracticaDjango/Blog/templates/Blog/detalle.html
{% extends "Proyecto_web_app/base.html" %}
{% 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|linebreaks }}
{% 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 %}
Para ver si todo funciona abre esta dirección en tu navegador (después de ejecutar el servidor) y haz click en el título de algún post que aun no tenga comentarios. Verás algo parecido a esto:
Rellena el formulario con datos que sean validos, baja un poco y dale al botón "añade comentario". Deberías ver la siguiente página.
Haz clic en el enlace "vuelve al post". Deberías ser redireccionado a la vista de "post_detalles", que tendría que contener el comentario que acabas de añadir.
No hay comentarios:
Publicar un comentario