Django class based views (III)


Escrit per Aaloy a 20 de February , 2012 a les 9:09 p.m.

En aquest apunt veurem com fer servir les generic class views per a fer feina amb formularis.

Seguirem la documentació de Django que tracta dels formularis. Allà ens fa referència a un formulari de contacte creat com:

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)

i la vista associada

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,
    })

El que farem ara és fer el mateix però fent servir les generic class views.

El primer que hem de veure és què hem de fer servir. Necessitam gestionar un formulari, la seva validació i tornar la redirecció cap a una nova plana web en cas que tot vagi bé.

Si feim una ullada a la jerarquia de classes del mòdul edit podem veure que la classes que compleix tot el que volem es diu FormView.

FormView està composada pel mixin TemplateResponseMixin que ja coneixem i BaseFormView que a la seva vegada està format per dos mixins més FormMixin i ProcessFormView.

FormMixin ens proporciona els mètodes per a gestionar el form i ProcessFormView que descendeix de View ens proporciona les respostes a les cridades get, post i put.

L'equivalent fent servir les generic class views ens queda:

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)

és a dir, hem de dir li a la classe quin formulari farem servir, això ho feim a form_class o bé amb la rescriptura del mètode get_form_class que ens ha proporcionat el mixin FormMixin o fin i tot instanciant el formulari directament si fem la rescriptura de get_form

Els mètodes form_valid i form_invalid ens permeten definir què em de fer amb el formulari. Normalment ens bastarà sobreescriure el mètode form_valid per a adaptar-lo a les nostres necessitats.

I qui crida al mètode is_valid del formulari? Doncs el post que prové de ProcessFormView. Recordem que un mixin té accés a tot l'objecte que el fa servir, i per tant també té accés als mètodes el mixin FormMixin.

Suposem ara que volem que el nostre formulari agafi uns certs valors inicials, això ho podem fer amb l'atribut initial definit a FormMixin o bé a partir de la reescriptura del mètode get_initial, segons la nostra aplicació ens convindrà un mètode o un altre.

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 a més hem de passar dades addicionals a la plantilla Django, FormMixin ens proporciona un altre vell conegut, el mètode get_context_data que fa el mateix que feia al TemplateView. Això sí, ara hem de tenir cura de cridar al mètode pare, ja que als kwargs hi ha la definició del formulari. Per a evitar-nos conèixer què fa i que no fa, millor cridar al mètode pare i a partir d'aquí afegir-hi les variables que necessitem:

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

I no hem de perdre de vista que estam fent feina amb classes. Podem anar afegint els mètodes que necessitem a la classe, de manera que cada funció ens quedi manejable.

I ja per acabar un petit avís, he fet servir reverse_lazy per a obtenir el valor de la url, però aquesta funció no hi és a Django 1.3. Per utilitzar el nom de la url enlloc de la url en sí, en Django 1.3 o bé hem de sobreescriure get_success_url o bé utilitzar l'snippet que apareix a Django Snippets i que resol això d'una manera molt elegant:

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

Això encara dóna per més, fins al proper article!


Traducciones/Translations by apertium

0 comentaris, 0 trackbacks (URL) , Tags: Python Django


Django class based views (II)


Escrit per Aaloy a 20 de February , 2012 a les 12:16 a.m.

En aquesta segona part veurem alguns dels usos més interessants de les class based views per a presentar informació i estalviar-nos feina.

TemplateView

Com ja vàrem veure a la primera part el TemplateView ens estalvia molta feina respecte a la manera tradicional de fer les coses, sols pel fet de passar ja el RequestContext ja recomanaria passar-nos al nou món de les Class Based Views, però encara hi ha més!

TemplateView incorpora dos mètodes més get_context_data i get. Amb el get el que fa es cridar al mètode render_to_response que ens proporciona el mixin TemplateResponseMixin i passant-li com a arguments el diccionari que ens ha de tornar el mètode get_context_data.

Per defecte get_context_data ens retorna un diccionari amb els paràmetres que venen de la url, així si la nostra url és

 url(r'^test3/(?P<id>\d+)/$', Test3View.as_view(), 
    name="test3-class-view"),

llavors get_context_data conté el diccionari {'id': 3} i a la plantilla es passarà una variable anomenada params amb aquest diccionari.

Suposem però que no és això exactament el que volem. Volem que passi a la plantilla una variable anomenanda msg amb una salutació per als lectors del blog.

El que farem serà sobreescriure get_context_data per a que passi la informació que nosaltres volem a la plantilla.

class Test3View(TemplateView):
    template_name = 'main/index.html'

    def get_context_data(self, **kwargs):
        context = super(Test3View, self).get_context_data(**kwargs)
        context['id'] = self.kwargs.get('id')
        context['msg'] = u'Hello blog!'
        return context

Estrictament parlant no faria falta cridar al mètode pare de get_context_data i de fet estam passant també la variable params però fer-ho no fa mal i ens proporciona un cert grau de protecció davant possibles problemes si un dia decidim que Test3View ja no heretarà de TemplateView sinó d'una altra classe on ja s'hi posi altra informació al contexte.

Al context_data i podem posar tota la informació que necessitem, fixem-nos que és el mateix que feiem amb les funcions que acabaven cridant al render_to_template sols que ara tot està més estructurat.

Suposem que volem que els missatge no estigui prefixat sinó que es generi a partir del resultat d'una funció. Abans hauríem creat la funció en algun lloc, a un mòdul apart, abans del mètode que renderitza la plantilla, ... Ara ho podem encapsular dins la mateixa classe

class Test3View(TemplateView):
    template_name = 'main/index.html'

    def get_message(self):
        return u'Hello blog'

    def get_context_data(self, **kwargs):
        context = super(Test3View, self).get_context_data(**kwargs)
        context = dict()
        context['id'] = self.kwargs.get('id')
        context['msg'] = self.get_message()
        return context

I encara tenim més flexibilitat, gràcies al mixin TemplateResponseMixin podem definir quina plantilla s'utilitzarà segons ens convingui

Suposem que el paràmetre id ens serveix per a definir el nom de la plantilla, de manera que si existeix una plantilla que hi casi la presentarà i si no es quedarà amb la plantilla per defecte. Podríem fer

class Test3View(TemplateView):
    template_name = 'main/index.html'

    def get_message(self):
        return u'Hello blog'

    def get_template_names(self):
        plantilla = 'main/index%s.html' % self.kwargs.get('id')
        return [plantilla, ] + super(Test3View, self).get_template_names()

    def get_context_data(self, **kwargs):
        context = super(Test3View, self).get_context_data(**kwargs)
        context['id'] = self.kwargs.get('id')
        context['msg'] = self.get_message()
        return context

Com veis la potència i la flexibilitat del que es pot fer amb les class based views no té res a veure amb el que es podia fer abans. Ara tenim molta més flexibilitat, més encapsulació i menys codi a escriure.

Al propert post parlarem de formularis...


Traducciones/Translations by apertium

0 comentaris, 0 trackbacks (URL) , Tags: Python Django