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
oPOST
. En caso deGET
generamos la plantilla HTML de la página. En caso dePOST
, 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, usamosget_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.
{{ comments.length }} comentarios