Class Based Views - Formularios

En este apunte veremos cómo utilizar las generic class views para trabajar con formularios. Utilizaremos el mismo ejemplo que Django utiliza cuando trata de formularios. En la documentación hace referencia a un formulario de contacto creado como:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

y la vista asociada

def contact(request):
    if request.method == 'POST': # If the form has been submitted...
        form = ContactForm(request.POST) # A form bound to the POST data
        if form.is_valid(): # All validation rules pass
            # Process the data in form.cleaned_data
            # ...
            return HttpResponseRedirect('/thanks/') # Redirect after POST
    else:
        form = ContactForm() # An unbound form

    return render_to_response('contact.html', {
        'form': form,
    })

Lo que haremos es ver cómo podemos tener esto mismo pero utilizando las generic class views. Lo primero es ver qué necesitamos: necesitamos gestionar un formulario, su validación y enviar la redirección hacia una nueva página web en caso de que todo vaya bien, es decir, que se pase la validación.

Si damos un vistazo a la jerarquía de clases del módulo edit podemos ver que la clase que cumple con todo lo que necesitamos recibe el nombre de FormView. FormView está formada por el mixin TemplateResponseMixin que ya conocemos y   BaseFormView que a su vez está formado por dos mixins más FormMixin i ProcessFormView.

FormMixin nos proporciona los métodos para gestionar el form y  ProcessFormView que hijo de View nos proporciona los métodos de respuesta a las llamadas GET, PUT y POST. El equivalente del código anterior utilizando las generic class views nos queda como:

class ContactView(FormView):
    form_class=ContactForm
    success_url = reverse_lazy('main_thanks')
    template_name = 'main/index.html'

    def form_valid(self, form):
        # process data
        print form.cleaned_data
        return super(ContactView, self).form_valid(form)

es decir, hemos de decirle a la clase qué formulario utilizaremos, esto lo hacemos en el atributo form_class o bien reescribiendo el método get_form_class que nos ha proporcionado el mixin FormMixin o instanciando el formulario directamente si sobrescribimos get_form

Los métodos form_valid y form_invalid nos permiten definir las acciones que realizaremos con el formulario. Normalmente nos bastará con sobrescribir o extender el método form_valid pera adapta-lo a nuestras necesidades.

Entonces, nos podemos preguntar, ¿quién llama al método is_valid del formulario? Si seguimos la jerarquía de clases veremos que lo hace el método post que es implementado por ProcessFormView.  Recordemos que un mixin tienen acceso a todo el objeto que lo utiliza, y por tanto también tiene acceso a los métodos del mixin FormMixin.

Supongamos que queremos que nuestro formulario tome unos ciertos valores iniciales. Esto lo podemos conseguir utilizando el atributo initial, definido a FormMixin, o bien a partir de la rescritura del método get_inital. Según cómo sea nuestra aplicación nos convendrá utilizar un método u otro.

class ContactView(FormView):
    form_class=ContactForm
    success_url = reverse_lazy('main_thanks')
    template_name = 'main/index.html'
    initial = {'subject': u'This is a test form'}

    def form_valid(self, form):
        # process data
        print form.cleaned_data
        return super(ContactView, self).form_valid(form)

 

Si además tenemos que pasar datos adicionales a la plantilla Django que renderiza el formulario, FormMixin nos proporciona un viejo conocido, el método get_context_data, cuyo funcionamiento es idéntico al que tenía en TemplateView. Importante! Ahora tenemos que cuidar que el método get_context_data extienda el método padre, ya que en el parámetro kwargs nos viene la definición del formulario. Para evitar tener que adentrarnos en las complejidades de qué hace o deja de hacer el método, es mucho mejor llamar al método padre y a partir de aquí añadirle las variables que necesitemos:

class ContactView(FormView):
    form_class=ContactForm
    success_url = reverse_lazy('main_thanks')
    template_name = 'main/index.html'
    initial = {'subject': u'This is a test form'}

    def form_valid(self, form):
        # process data
        print form.cleaned_data
        return super(ContactView, self).form_valid(form)

    def get_context_data(self, **kwargs):
        context = super(ContactView, self).get_context_data(**kwargs)
        context['msg'] = u'Hello World'
        return context

Y no perdamos de vista que estamos trabajando utilizando clases y programación orientada a objetos, por lo que podemos ir añadiendo los métodos que necesitemos a la clase de manera que cada función por sí misma nos quede manejable.

Y ya para acabar el post un pequeño aviso, he utilizado reverse_lazy pera obtener el valor de la url, pero esta funció no está en Django 1.3. Para utilizar el nombre de la url en lugar de la url en sí, en Django 1.3 hemos de sobreescribir get_success_url o bien utilizar un snippet que podemos encontrar en   Django Snippets que resuelve el problema de manera muy elegante

from django.utils.functional import lazy
from django.core.urlresolvers import reverse
reverse_lazy = lambda name=None, *args : lazy(reverse, str)(name, args=args)

En el próximo artículo veremos cómo podemos acceder y mostrar un objeto.

Comentarios
  1. Ornitorrinco Enmascarado Ornitorrinco Enmascarado on 01/03/2012 09:54 #

    Lo primero, agradecerte y felicitarte por estos tutoriales, están muy interesantes y bastante
    trabajados, en mi modesta opinión, más comprensibles que en el tutorial oficial.

    Una duda que se me plantea es si es posible incluir en el fichero url las vistas usando una string, como con las vistas normales, o si es necesario incluir la instancia de la clase. Si fuera posible, ¿cual sería la sintaxis para pasarle los parámetros al constructor, si los tuviese?

  2. aaloy aaloy on 01/03/2012 10:28 #

    No, en las urls necesitas hacer la llamada a la función as_view, así que nada de llamadas como string.

    Una cosa que se puede hacer es configurar la classe dentro de la llamada a la url, como puse en el primer ejemmplo. Personalmente no me gusta, ya que acabas haciendo más ilegible el urls.py y tener este archivo limpio va muy bien a la hora de trabajar. Después de todo "la legibilidad también cuenta".

Los pingbacks están cerrados.

Trackbacks