domingo, 26 de febrero de 2023

11.- Django. API Forms

Django tiene principalmente dos clases para construir formularios: Form y ModelForm.

La primera es la que veremos en este capitulo. La segunda ModelForm se utiliza para crear formularios de forma dinámica a partir de los campos contenidos en modelos. La veremos en más detalle más adelante pero si quieres echarle un vistazo puedes consultar la documentación en https://docs.djangoproject.com/en/4.1/topics/forms/modelforms/.

Vamos con el uso de la clase Form.

 Documentación API Form.

Nos va a permitir crear y validar formularios de forma sencilla.

Para utilizarla lo primero que tenemos que hacer es crear en cualquier lugar de nuestro proyecto (aunque por convención se suele realizar en el mismo directorio que tengamos el views.py) un archivo llamado forms.py y dentro crearemos una clase para cada formulario que queramos crear.

gestionPedidos/forms.py

Una vez creado el archivo lo primero que tenemos que hacer es importar esta clase forms, y una vez importada crearemos una clase cuya instancia será el formulario de contacto que queremos crear. A la clase la podemos llamar como queramos y dentro de los argumentos hay poner forms.Form. A continuación especificaremos los campos que tendrá el formulario que construya esta clase.

gestionPedidos/forms.py

from django import forms

class FormularioContacto(forms.Form):
    #Especificamos los campos del formulario
    asunto = forms.CharField()
    email = forms.EmailField()
    # etiqueta textarea en django
    mensaje = forms.CharField(widget=forms.Textarea(attrs={'cols': 45, 'rows': 15}))

La clase Form es el corazón del sistema de manejo de formularios de Django. Especifica los campos en el formulario, su diseño, widgets de visualización, etiquetas, valores iniciales, valores válidos y (una vez validados) los mensajes de error asociados con campos no válidos.

Los campos que podemos utilizar en un formulario son los siguientes:

BooleanFieldCharFieldChoiceFieldTypedChoiceFieldDateField
DateTimeFieldDecimalFieldDurationFieldEmailFieldFileField
FilePathFieldFloatFieldImageFieldIntegerFieldGenericIPAddressField,
MultipleChoiceFieldTypedMultipleChoiceFieldNullBooleanFieldRegexFieldSlugField
TimeFieldURLFieldUUIDFieldComboFieldMultiValueField
SplitDateTimeFieldModelMultipleChoiceFieldModelChoiceField.

Los argumentos que son comunes a la mayoría de los campos anteriores son:

  • required: Si es True el campo no se puede dejar en blanco o dar un valor None. Los Campos son obligatorios por defecto, también puedes establecer required=False para permitir valores en blanco en el formulario.
  • label: label es usado cuando renderizamos el campo en HTML. Si label no es especificado entonces Django crearía uno a partir del nombre del campo al poner en mayúscula la primera letra y reemplazar los guiones bajos por espacios (por ejemplo. Renewal date).
  • label_suffix: Por defecto, se muestran dos puntos después de la etiqueta. Este argumento le permite especificar como sufijo diferente que contiene otros caracteres.
  • initial: El valor inicial para el campo cuando es mostrado en el formulario.
  • widget: El widget de visualización para usar.
  • help_text texto adicional que se puede mostrar en formularios para explicar cómo usar el campo.
  • error_messages: Una lista de mensajes de error para el campo. Puede reemplazarlos con sus propios mensajes si es necesario.
  • validators: Una lista de funciones que se invocarán en el campo cuando se valide.
  • localize: Permite la localización de la entrada de datos del formulario (consulte el enlace para obtener más información).
  • disabled: El campo se muestra pero su valor no se puede editar si esto es True. Por defecto es False.
Para ver como ha quedado y como crea Django el formulario vamos a abrir el shell de django para ver el resultado.

$ python manage.py shell
>>> from gestionPedidos.forms import FormularioContacto
>>> prueba = FormularioContacto()
>>> print(prueba)
La salida es el código html que se genera al instanciar la clase.

Salida:

<tr>
    <th><label for="id_asunto">Asunto</label></th>
    <td>      
      <input type="text" name="asunto" required id="id_asunto">     
    </td>
</tr>

<tr>
    <th><label for="id_email">Email:</label></th>
    <td>      
      <input type="email" name="email" required id="id_email">
     </td>
</tr>

<tr>
    <th><label for="id_mensaje">Mensaje:</label></th>
    <td>      
      <textarea name="mensaje" cols="45" rows="15" required id="id_mensaje">
      </textarea>           
    </td>
</tr>

Como se ve se lo esta formateando automáticamente con forma de tabla. Por defecto todos los campos son requeridos y también se les añade los label automáticamente.

Pero también podemos darle formato de párrafo (con etiquetas <p>).

$ python manage.py shell
>>> from gestionPedidos.forms import FormularioContacto
>>> prueba = FormularioContacto()
>>> print(prueba.as_p())
SALIDA


<p>
    <label for="id_asunto">Asunto</label>
    <input type="text" name="asunto" required id="id_asunto">
</p>

<p>
    <label for="id_email">Email:</label>
    <input type="email" name="email" required id="id_email">
</p>

<p>
    <label for="id_mensaje">Mensaje:</label>
    <textarea name="mensaje" cols="45" rows="15" required id="id_mensaje">
    </textarea>
</p>

Pero también se puede crear como una lista desordenada.

$ python manage.py shell
>>> from gestionPedidos.forms import FormularioContacto
>>> prueba = FormularioContacto()
>>> print(prueba.as_ul())
SALIDA

<li>
    
    <label for="id_asunto">Asunto</label>
    <input type="text" name="asunto" required id="id_asunto">
</li>

<li>
    <label for="id_email">Email:</label>
    <input type="email" name="email" required id="id_email">
</li>

<li>
    <label for="id_mensaje">Mensaje:</label>
    <textarea name="mensaje" cols="45" rows="15" required id="id_mensaje">
    </textarea>
</li>

Como se ve, django no incluye las etiquetas <table> ni tampoco <form>. Lo que si hace Django es validar los datos introducidos en los campos. Lo primero es que hemos visto es que los campos son requeridos, pero también le da otros tipos de validaciones. Por ejemplo en el campo email si se introduce un email no válido no lo permitiría. Esto implica que a la hora de enviar un formulario este puede ser válido o no (por ejemplo un email incorrecto o dejar en blanco un campo requerido)

Para saber si un formulario es válido tenemos el método dentro de la api forms que es is_valid(). Este método nos dice si ha pasado la validación, devolviendo True si es correcta o Falsa si no lo es. Si este método devuelve True podremos usar una propiedad que es cleaned_data. Esta propiedad devuelve los campos enviados pero ya validados. 

Vamos a verlo con un ejemplo. Creemos un formulario pasándole los campos como argumentos a través de un diccionario.

>>> mi_formulario=FormularioContacto({'asunto':'prueba','email':'usuario@correo.es','mensaje':'texto de prueba',})
>>> mi_formulario.is_valid()
True
>>> mi_formulario.cleaned_data
{'asunto': 'prueba', 'email': 'usuario@correo.es', 'mensaje': 'texto de prueba'}
>>> 


Vamos a aplicar todo esto a nuestro proyecto. Vamos al archivo de vista de contacto, donde ya teníamos un código que funcionaba correctamente, lo comentamos y vamos a crear uno nuevo con esto que hemos visto. Lo primero es importar la clase que hemos creado para poder crear el formulario.


gestionPedidos/views.py

# Para poder enviar emails a través del formulario de contacto.
from django.core.mail import send_mail
from django.conf import settings
# api de formulario
from gestionPedidos.forms import FormularioContacto

...
def contacto(request):
    # Costruyendo el mismo formulario mediante API forms
    if request.method=="POST":
        mi_formulario=FormularioContacto(request.POST)
        if mi_formulario.is_valid():
            informacion_formulario = mi_formulario.cleaned_data
            send_mail(
                informacion_formulario['asunto'],
                informacion_formulario['mensaje']+' '+ informacion_formulario['email'],
                settings.EMAIL_HOST_USER,
                [settings.EMAIL_DESTINO],
            )
            return render(request, 'gracias.html')
    else: # metodo GET
        mi_formulario=FormularioContacto()
    
    return render(request, 'formulario_contacto_api.html', {'form':mi_formulario})


La primera vez que entramos en la vista se renderiza un formulario en blanco ya que el método utilizado no es el método POST. La plantilla 'formulario_contacto_api.html' aun no esta creada pero lo haremos luego. A esa plantilla le pasamos el código html con la creación del formulario. Una vez que se rellena el formulario comprobamos que este sea válido y si lo es, pasamos la información ya filtrada a la variable informacion_formulario con la propiedad cleaned_data.  Luego accedemos a los valores de los campos a través del diccionario. 

Ya solo queda crear el archivo 'formulario_contacto_api.html'

gestionPedidos/templates/formulario_contacto_api.html

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contacto</title>
</head>
<body>
    <h1>Contacta con nosotros.</h1>
    {% if form.errors %}
    <p style="color: red;">Por favor,revisa este campo.</p>
    {% endif %}

    <form action="" method="post">
        {% csrf_token %} 
        <table>
            {{ form.as_table}}
        </table>
        <input type="submit" value="Enviar">
    
    </form>
</body>
</html>

La API forms crea una validación por defecto, pero aun así que lo primero que haremos es prever un posible error con {% if forms.errors %} con su código para validarlo. Luego ponemos las etiquetas <form>, el token, el <table> y dentro la API form ya crea el formulario por nosotros.

El resultado es el siguiente. Si intentas por ejemplo introducir un correo electrónico no válido o dejar un campo requerido en blanco el propio formulario te avisará de ello.

Formulario Generado con el APi form

En el ejemplo anterior hemos instanciado el formulario como una tabla {{ form.as_table }}. Le hemos dicho a Django que renderice los campos del formulario usando elementos HTML para diseñar una tabla. También podríamos haberlos renderizado como un párrafo con as_p o como una lista desordenada usando as_ul. Otra opción sería renderizar cada campo iterando a través de cada uno de los campos del formulario, como en el siguiente ejemplo:


{% for field in form %}

<div>

{{ field.errors }}
{{ field.label_tag }} {{ field }}

</div>
{% endfor %}
  

Como te habrás dado cuenta también hemos añadido el {{ csrf_token }}. Esta etiqueta añade al formulario un campo oculto con un token autogenerado para evitar el ataque maliciosos cross-site request forgery (CSRF). Puedes encontrar más información sobre este ataque en https://owasp.org/www-community/attacks/csrf.

La plantilla de esta etiqueta genera un campo oculto que se renderiza de esta forma:

<input type='hidden' name='csrfmiddlewaretoken'
value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />

Por defecto Django siempre comprueba si esta esta etiqueta en todos las solicitudes ('request') que se hacen a través del método POST, por tanto recuerda incluirla en todos los formularios que se envíen a través del método POST.



No hay comentarios:

Publicar un comentario