El libro de Django 1.0

7.3. Creación de un formulario para comentarios

La mejor forma de construir un sitio que guste a la gente es atendiendo a sus comentarios. Muchos sitios parecen olvidar esto y ocultan los detalles de su contacto lo máximo posible.

Cuando tu sitio tiene millones de usuarios, esto puede ser una estrategia razonable. En cambio, cuando intentas formarte una audiencia, deberías pedir comentarios cada vez que se presente la oportunidad. Escribamos entonces un simple formulario para comentarios, y usémoslo para entender cómo funciona el framework de Django.

Comenzaremos agregando (r'^contact/$', 'mysite.books.views.contact') al URLconf, y luego definamos nuestro formulario. Los formularios en Django se crean de una manera similar a los modelos: declarativamente, empleando una clase de Python. He aquí la clase para nuestro simple formulario. Por convención, lo insertaremos en un nuevo archivo forms.py dentro del directorio de nuestra aplicación:

from django import newforms as forms

TOPIC_CHOICES = (
    ('general', 'General enquiry'),
    ('bug', 'Bug report'),
    ('suggestion', 'Suggestion'),
)

class ContactForm(forms.Form):
    topic = forms.ChoiceField(choices=TOPIC_CHOICES)
    message = forms.CharField()
    sender = forms.EmailField(required=False)

Un formulario de Django es una subclase de django.newforms.Form, tal como un modelo de Django es una subclase de django.db.models.Model. El módulo django.newforms también contiene cierta cantidad de clases Field para los campos. Una lista completa de éstas últimas se encuentra disponible en la documentación de Django.

Nuestro ContactForm consiste de tres campos: un asunto (topic), que se puede elegir entre tres opciones; un mensaje (message), que es un campo de caracteres; y un remitente (sender), que es un campo de correo electrónico y es opcional (porque incluso los comentarios anónimos pueden ser útiles). Hay una cantidad de otros tipos de campos disponibles, y puedes escribir nuevos tipos si ninguno cubre tus necesidades.

El objeto formulario sabe cómo hacer una cantidad de cosas útiles por sí mismo. Puede validar una colección de datos, puede generar sus propios "widgets" de HTML, puede construir un conjunto de mensajes de error útiles. Y si no quieres diseñar el formulario a mano, puede incluso renderizar el formulario entero. Incluyamos esto en una vista y veámoslo en acción. En views.py:

from django.db.models import Q
from django.shortcuts import render_to_response
from models import Book
from forms import ContactForm

def search(request):
    query = request.GET.get('q', '')
    if query:
        qset = (
            Q(title__icontains=query) |
            Q(authors__first_name__icontains=query) |
            Q(authors__last_name__icontains=query)
        )
        results = Book.objects.filter(qset).distinct()
    else:
        results = []
    return render_to_response("books/search.html", {
        "results": results,
        "query": query
    })

def contact(request):
    form = ContactForm()
    return render_to_response('contact.html', {'form': form})

y en contact.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>
    <form action="." method="POST">
        <table>
            {{ form.as_table }}
        </table>
        <p><input type="submit" value="Submit"></p>
    </form>
</body>
</html>

La línea más interesante aquí es {{ form.as_table }}. form es nuestra instancia de ContactForm, que fue pasada al render_to_response. as_table es un método de ese objeto que reproduce el formulario como una secuencia de renglones de una tabla (también pueden usarse as_ul y as_p). El HTML generado se ve así:

<tr>
    <th><label for="id_topic">Topic:</label></th>
    <td>
        <select name="topic" id="id_topic">
            <option value="general">General enquiry</option>
            <option value="bug">Bug report</option>
            <option value="suggestion">Suggestion</option>
        </select>
    </td>
</tr>
<tr>
    <th><label for="id_message">Message:</label></th>
    <td><input type="text" name="message" id="id_message" /></td>
</tr>
<tr>
    <th><label for="id_sender">Sender:</label></th>
    <td><input type="text" name="sender" id="id_sender" /></td>
</tr>

Observa que las etiquetas <table> y <form> no se han incluido; debes definirlas por tu cuenta en la plantilla. Esto te da control sobre el comportamiento del formulario. Los elementos label sí se incluyen, y permiten mejorar la accesibilidad de los formularios.

Nuestro formulario actualmente utiliza un widget <input type="text"> para el campo del mensaje. Pero no queremos restringir a nuestros usuarios a una sola línea de texto, así que la cambiaremos por un widget <textarea>:

class ContactForm(forms.Form):
    topic = forms.ChoiceField(choices=TOPIC_CHOICES)
    message = forms.CharField(**widget=forms.Textarea()**)
    sender = forms.EmailField(required=False)

El framework de formularios divide la lógica de presentación para cada campo, en un conjunto de widgets. Cada tipo de campo tiene un widget por defecto, pero puedes sobreescribirlo fácilmente, o proporcionar uno nuevo desarrollado por ti.

Por el momento, si se suministra el formulario, no sucede nada. Agreguemos nuestras reglas de validación:

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
    else:
        form = ContactForm()
    return render_to_response('contact.html', {'form': form})

Una instancia de formulario puede estar en uno de dos estados: bound (vinculado) o unbound (no vinculado). Una instancia bound se construye con un diccionario (o un objeto que funcione como un diccionario) y sabe cómo validar y volver a representar sus datos. Un formulario unbound no tiene datos asociados y simplemente sabe cómo representarse a sí mismo.

Intenta hacer clic en Submit en el formulario vacío. La página se volverá a cargar, mostrando un error de validación que informa que nuestro campo de mensaje es obligatorio.

Intenta también ingresar una dirección de correo electrónico inválida. El EmailField sabe cómo validar estas direcciones, por lo menos a un nivel razonable.