Django crear formulario de contacto en AJAX y asíncrono

4 minutos

Django

Realizar un formulario de contacto en Django puede enfocarse de diferente formas. La más común es usar una vista y una plantilla HTML, en poco tiempo se puede disponer de un formulario funcional. La complejidad llega cuando quieres ofrecer una experiencia atractiva y sin recargas logrando pura inmediatez.

Si la comunicación entre el cliente y el servidor se realiza de manera asíncrona, y a su vez Django envía el correo sin hacer esperar al usuario, sin duda la más absoluta rapidez. El sitio reflejará profesionalidad.

Los objetivos del ejemplo son:

  • Comunicar el BackEnd y el FrontEnd por medio de AJAX.
  • Usar el módulo de forms para la validación de datos desde Django.
  • Enviar el mensaje por email de manera asíncrona para no hacer esperar al visitante.
  • Usar plantillas de correos tanto en HTML como texto plano.
  • Enviar archivos, de forma opcional.

1. Declarando formulario.

Creamos forms.py dentro del App que dispongamos con el siguiente contenido.

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(
        max_length=200,
        initial="",
        required=True,
    )
    email = forms.EmailField(
        max_length=200,
        initial="",
        required=True,
    )
    message = forms.CharField(
        max_length=200,
        initial="",
        required=True,
    )
    files = forms.FileField(
        widget=forms.ClearableFileInput(attrs={"multiple": True}), required=False
    )

Puedes editar los parámetros del formulario a tu gusto.

2. Creando vista.

Crea el siguiente HTML en la carpeta de templates con el nombre de contact.html.


<!doctype html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>
<body>
    <form id="form" novalidate>
        {% csrf_token %}
        {{ form.name.label }}
        {{ form.name }}
        {{ form.email.label }}
        {{ form.email }}

        {{ form.message.label }}
        {{ form.message }}

        {{ form.files.label }}
        {{ form.files }}

        <input id="submit" type="submit" value="Enviar">

        <script>
                //// Variables
                // Get csrf token
                const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
                const form = document.querySelector('#form');

                //// Functions

                function sendForm(event) {
                    event.preventDefault();
                    const request = new Request(
                        "{% url 'contact' %}",
                        {
                            method: 'POST',
                            headers: {'X-CSRFToken': csrfToken},
                            mode: 'same-origin',
                            body: new FormData(form)
                        }
                    );
                    fetch(request)
                        .then((response) => {
                            if (response.ok) {
                                return response.json();
                            }
                            throw new Error('Network response was not ok.');
                        })
                        .then(function(data) {
                            console.log(data)
                            if (data.status === 'ok') {
                                alert('Mensaje enviado correctamente');
                            } else {
                                alert('Error al enviar el mensaje');
                            }
                        });
                }

                //// Events
                form.addEventListener('submit', sendForm);

        </script>
    </form>
</body>
</html>

  • Imprimimos el CSRF token para validar que la información proviene de nuestro FrontEnd.
  • Imprimimos el formulario con sus campos.
  • Con JavaScript, capturamos el token CSRF para incluirlo en el header y realizamos una petición AJAX con los campos del formulario.

En el view.py, crea la siguiente vista.

from .forms import ContactForm
from django.http import JsonResponse
from django.shortcuts import render
import threading

def contact(request):
    form = ContactForm(request.POST, request.FILES)

    if request.method == "POST":
        if form.is_valid():
            # Get all url files
            files = request.FILES.getlist("files")
            files_paths = get_paths_to_files_stored_in_memory(files)
            # Send email in background
            threading_emails = threading.Thread(
                target=send_email,
                args=(
                    subject="Nuevo aviso",
                    to=["mi@correo.com"],
                    template_txt="contact_email.txt",
                    template_html="contact_email.html",
                    data={
                        "name": form.cleaned_data.get("name"),
                        "email": form.cleaned_data.get("email"),
                        "message": form.cleaned_data.get("message"),
                    },
                    attachments=files_paths,
                ),
            )
            threading_emails.start()
            # Response
            return JsonResponse({"status": "success"})
        else:
            return JsonResponse({"status": "ko", "errors": form.errors})
    return render(request, "public/contact.html", {"form": form})


  • Recogemos la información proveniente del formulario con ContactForm.
  • Comprobamos si recibimos la petición con GET o POST. En caso de GET generamos la plantilla HTML de la página. En caso de POST, comprobamos las validaciones del formulario que se cumplan, y empezamos el proceso de envío. En caso que no se cumplan las validaciones, se envían los mensajes de error.
  • Recogemos todos los archivos con request.FILES.getlist. Como están en memoria, usamos get_paths_to_files_stored_in_memory para obtener sus rutas.
  • Creamos un hilo de ejecución con Thread al enviar el correo de manera asíncrona.

Para facilitar la funcionalidad de enviar un correo en doble formato (texto plano y HTML), además de adjuntar archivos, he creado una función para ello: send_mail. Al igual que obtener las rutas de ficheros en memoria, ya que los archivos enviados desde el formulario nunca llegaremos a almacenarnos en el disco. Hay que guardarlos en temporales con get_paths_to_files_stored_in_memory.

Puedes añadir a la vista, views.py, el siguiente código o alojarlo en un fichero individual para luego importarlo.

import os
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile


def send_email(
    subject="Nuevo aviso",
    to=[],
    template_txt="",
    template_html="",
    data={},
    attachments=[],
):
    """Send email"""
    msg = EmailMultiAlternatives(
        subject,
        render_to_string(template_txt, data),
        settings.DEFAULT_FROM_EMAIL,
        to,
    )
    msg.attach_alternative(render_to_string(template_html, data), "text/html")
    for attachment in attachments:
        msg.attach_file(attachment)
    msg.send()


def get_paths_to_files_stored_in_memory(files):
    """Get paths to files stored in memory"""

    def get_url_from_memory_file(file):
        path = default_storage.save("tmp/" + str(file), ContentFile(file.read()))
        return os.path.join(settings.MEDIA_ROOT, path)

    return list(map(get_url_from_memory_file, files))

Por último añade la ruta en urls.py de la vista que acabamos de crear.

urlpatterns = [
    path("", home, name="home"),
    path(r"contacto/", contact, name="contact"),
]

3. Creando plantillas de correos.

No todos los clientes de correo visualizaran los mensajes en HTML (temas de seguridad, antigüedad, preferencia del usuario…), por lo cual es recomendable acompañarlo de una edición en texto plano (txt).

Creamos una plantilla llamada contact_email.txt.

Hola!

Has recibido un mensaje de contacto desde la página web.

Nombre: 
Email: 
Mensaje: 

Y otra con su versión en HTML con el nombre contact_email.html.


<p>
    Hola!
</p>
<p>
    Has recibido un mensaje de contacto desde la página web.
</p>
<p>
    <strong>Nombre:</strong> {{ name }}
</p>
<p>
    <strong>Email:</strong> {{ email }}
</p>
<p>
    <strong>Mensaje:</strong> {{ message }}
</p>

Conclusión

No hemos entrado en detalles como funciona Django porque doy por sentado que tienes experiencia creando sitios dinámicos con este fantástico Framework. No obstante, aunque te topes con conceptos que no termines de entender, si que puede servirte como base para construir otras soluciones. Espero que de alguna manera sea de ayuda.

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me ayudas?

No te sientas obligado a realizarme una donación, pero cada aportación me ayuda a mantener el sitio en activo para que continúe existiendo y me motiva a continuar creando nuevo contenido.

Comprame un café
Pulsa sobre la imagen
  • 1 café: Se mantiene el dominio durante 4 meses.
  • 2 cafés: Se paga 1 mes de servidor.
  • 3 cafés: Se cubre 1 mes de Black box.

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario

Tal vez también te interese...