Django class based views (VII) - Epíleg


Escrit per Aaloy a 24 de February , 2012 a les 12:02 a.m.

El món de les class based views dóna per molt, la possibilitat de sobreescriure funcions, canviar paràmetres i anar combinant mixins fins a obtenir el que necessitam ens permet reutilitzar molt de codi i de manera elegant.

En aquest darrer post de la sèrie veurem algunes de les situacions més habituals en les que ens podem trobar i la facilitat amb que es resolen.

El formulari per defecte no és el que vull

Sovint quan editam un objecte mitjançant un formulari ens trobam que hi ha camps que no volem que s'editin. A l'exemple que hem fet servir fins ara imaginem que no volem que una vegada s'ha crear l'slug es pugui modificar.

Per fer això el que hem de fer és crear un formulari nou. Com que es bo tenir les coses organitzades ho farem dins el fitxer forms.py

class SampleEditForm(forms.ModelForm):
    class Meta:
        model = Sample
        exclude = ('slug',)

i llavors a la classe que implementa l'edició li direm que agafi aquest formulari per a editar l'objecte

from forms import SampleEditForm

class SampleUpdateView(UpdateView):
    model = Sample
    success_url = reverse_lazy('tutorial_list_sample')
    form_class=SampleEditForm

Volem poder filtrar/ordenar els resultats

El ListView és un component idial per a fer cerques i filtres. Amb una fulla d'estils i un poc de javascript podem fer gairebé de tot. Per a no complicar-ho massa suposem que el que volem és establir dos filtres: un per quantitats positives i un de negatives, a més de l'opció per defecte que serà la de mostar-ho tot.

Afegirem els links dels filtres:

<a href="{% url tutorial_list_sample %}">All</a>|
<a href="{% url tutorial_list_sample %}?filter=positive">Positive</a>|
<a href="{% url tutorial_list_sample %}?filter=negative">Negative</a>

i ara a la classe el que farem serà sobrescriure el mètode get_queryset de manera que tengui en compte si estam filtrant o no i que apliqui el que correspon

class SampleListView(ListView):
    model = Sample
    paginate_by = 5

    def get_queryset(self):
        queryset = super(SampleListView, self).get_queryset()
        filter = self.request.GET.get('filter')
        if filter == 'positive':
            queryset = queryset.filter(ammount__gt=0)
        elif filter== 'negative':
            queryset = queryset.filter(ammount__lt=0)
        return queryset

Com es pot veure als exemples al final es tracta de'anar cercant el que s'ha de sobreescriure o extendre per a adaptar-lo a les nostres necessitats.

No vull acabar la sèrie sense referir-me a tota la part de Class Based Views orientades a mostrar informació que ha d'estar relacionada amb dates. Aquestes classes es troben a django.views.generic.dates i amb el que hem vist de ben segur que no tindreu cap dificultat per a fer-les servir.

Esper que us hagi agradat la sèrie i que us sigui profitosa. L'objectiu ha estat introduïr aquesta manera de fer les coses i organitzar el codi, que per projectes amb un bon grapat de models i manteniments, resulta amb menys codi, més ordenat i sobretot més mantenible i reaprofitable.


Traducciones/Translations by apertium

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


Django class based views (VI) - Lists


Escrit per Aaloy a 23 de February , 2012 a les 8:44 p.m.

En aquesta sisena entrega (que hi ha algú per aquí ho ja us heu dormit tots?) veurem com podem mostrar llistes d'objectes que sovint podem trobar amb els CRUD.

Per a mostrar llistats (paginats o no) Django ens proporciona la classe ListView que es pot trobar a dango.views.generic.list.

Aquesta classe és hereva de MultipleObjectTemplateResponseMixin i de BaseListView.

BaseListView és el que la la feina, ja que és fill de MultipleObjectMixin i de View implementant-ne el mètode get.

Com les vistes orientades als CRUD també en aquest cas tenim una plantilla per defecte, formada pel nom del model i el sufixe '_list'.

El que més ens interessa són doncs els mètodes i atributs de MultipleObjectMixin. Primer de tot, però anem a fer-ho fàcil, mostrarem la llista dels objectes que hem creat al post anterior:

A l'url:

url(r'^tutorial/sample/list/$', SampleListView.as_view(),
    name='tutorial_list_sample'),

i al views.py podem fer

from django.views.generic.list import ListView

class SampleListView(ListView):
    model = Sample

i ara haurem de definir la plantilla que tindrà com a nom sample_list.html, primer, però és convenient conèixer quines variables es passen al context, a saber:

  • paginator
  • page_obj
  • is_paginated
  • object_list

Els valors poden canviar en funció de si tenim paginació o no, però les variables que tenim són aquestes. El que ens interessa per ara és object_list que conté la llista d'objectes del model. De manera que podem fer una plantilla del tipus:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>test</title>
</head>
<body>
    <h1>Sample Detail List</h1>
    <table border="1">

    {% for sample in object_list %}
    <tr>
        <td><a href="{% url tutorial_update_sample sample.slug %}">
            {{sample.slug}}</a><td/>
        <td>{{sample.name}}<td/>
        <td>{{sample.ammount}}<td/>
        <td>{{sample.comments}}<td/>
        <td>
            <a href="{% url tutorial_delete_sample sample.slug %}">Delete</a>|
            <a href="{% url tutorial_create_sample %}">Create</a>
        </td>
        </tr>
    {% endfor %}
    </tr>
    </table>
</body>
</html>

Fixau-vos que no estic fent herència, l'html és molt simple etc. L'important aquí és veure com podem recórrer la llista d'objectes per montar les files d'una taula, i com amb el que ja sabem del CRUD podem enllaçar amb els manteniments.

Ara a l'hora de crear, editar o esborrar un registre podem elegir entre mostrar el registre o anar al llistat, afegint

success_url = reverse_lazy('tutorial_list_sample')

a les vistes que ens interessa ja ho tindriem.

Paginació


Com podem tenir molts resultats és comú que les llistes es mostrin paginades. Per fer això el que hem d'afegir l'atribut paginate_by que ens determinarà el nombre màxim d'elements que es mostraran per plana.

 class SampleListView(ListView):
    model = Sample
    paginate_by = 2

Si fem això i no modificam la plantilla, sols veurem dos registres, el que ens falta ara és afegir els controls de plana anterior i plana següent. Hi ham moltes maneres de fer la paginació i a la web en trobareu un bon munt, però per senzillesa aquest

{% if is_paginated %}
        {% if page_obj.has_previous %}
          <a href="{% url tutorial_list_sample %}?page={{ page_obj.previous_page_number }}">
             Previous</a>
        {% endif %}
            <!-- current page -->
                Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
            <!-- end current page -->
        {% if page_obj.has_next %}
         <a href="{% url tutorial_list_sample %}?page={{ page_obj.next_page_number }}">
                Next</a>
         {% endif %}
{% endif %}

Recordau que hem dit que al context es passava page_object? Doncs fixau-vos com es fa servir per saber les propietats de la plana on estam, el nombre de planes total, si la plana té una plana anterior o una plana següent i el nombre corresponent a aquesta plana.

També fem us del is_paginated de manera que no mostrem el selector de plana anterior i següent si sols hi ha una plana.

I ja ho tenim!

Al proper post, que tancarà la sèrie, veurem com podem limitar la informació que mostram als formularis d'edició


Traducciones/Translations by apertium

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


Django class based views (V) - CRUD


Escrit per Aaloy a 22 de February , 2012 a les 9:38 p.m.

Creant un CRUD

En aquesta cinquena part veurem com podem crear tot el relacionat amb un manteniment, el famós CRUD (Create, Retreive, Update, Delete).

La part de Retreive ja l'hem vista a l'article anterior, però hi tornarem per a que ens quedi un exemple complet.

Partirem del següent model:

class Sample(models.Model):
    """this is just a sample model"""

    slug = models.SlugField(max_length=50, unique=True)
    name = models.CharField(max_length=100)
    ammount = models.IntegerField()
    comments = models.TextField(blank=True, null=True)

    class Meta:
        verbose_name = 'Sample'
        verbose_name_plural = 'Sample'

    def __unicode__(self):
        return self.name

Django a més de DetailView ens proporciona les següents vistes ja construïdes per fer aquest tipus de tasques:

* `CreateView` que gestiona la creació
* `UpdateView` que gestiona l'actualització
* `DeleteView` gestiona l'esborrat

Comencem!

Creació

Definim primer la url

url(r'^tutorial/sample/create/$', SampleCreateView.as_view(),
    name='tutorial_create_sample'),

Hem anomenat a la nostra classe SampleCreateView així que la definirem al views.py

from django.views.generic.edit import CreateView
from main.models import Sample

class SampleCreateView(CreateView):
    model = Sample

Amb això Django ja ens crearà internament un formulari i l'intentarà presentar a una plantilla que es construeix amb el nom del model i el sufixe _form, és a dir, en el nostre cas sample_form.html passant com a variable de context el formulari creat form

Si volem fer via amb :

    <form action="."
            method="post" accept-charset="utf-8">
            {% csrf_token %}
        <table>
        {{form}}
        </table>
        <p><input type="submit" value="Save &rarr;"></p>
    </form>

ja ens anirà bé. Django ja se n'encarrega de validar el formulari contra el nostre model. És a dir, si posam quelcom que no sigui un nombre sencer a amount ens generarà un error.

Si creau la plantilla i posau dades vàlides i intentau guardar us donarà un error: "No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model.", poc a poc i ara hi anirem a això.

Mostram l'objecte

Encara que ja ho vàrem veure com es feia al post anterior, per completitud, ho farem també ara:

La url:

    url(r'^tutorial/sample/retrieve/(?P<pk>\d+)/$', 
        SampleView.as_view(),
        name='tutorial_view_sample'),

i al views.py

class SampleView(DetailView):
    model = Sample

Recordau l'error del que hem parlat abans? Doncs el que farem és modificar el model per definir el `get_absolute_url, també podíem haver definit success_url o sobreescrit get_success_url però això ja ho vàrem veure, així que variarem un poc.

Modificam el nostre model

class Sample(models.Model):
    """this is just a sample model"""

    slug = models.SlugField(max_length=50, unique=True)
    name = models.CharField(max_length=100)
    ammount = models.IntegerField()
    comments = models.TextField(blank=True, null=True)

    class Meta:
        verbose_name = 'Sample'
        verbose_name_plural = 'Sample'

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        return ('tutorial_view_sample', [self.id, ])

També podíem haver fet el mateix fent servir l'slug, amb la qual cosa tindríem urls més amigables. Per això hem de canviar tant la url com el get_absolute_url

class Sample(models.Model):
    """this is just a sample model"""

    slug = models.SlugField(max_length=50, unique=True)
    name = models.CharField(max_length=100)
    ammount = models.IntegerField()
    comments = models.TextField(blank=True, null=True)

    class Meta:
        verbose_name = 'Sample'
        verbose_name_plural = 'Sample'

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        return ('tutorial_view_sample', [self.slug, ])

i la url:

url(r'^tutorial/sample/retrieve/(?P<slug>[-\w]+)/$', 
    SampleView.as_view(),
    name='tutorial_view_sample'),

Què fa el permalink? doncs, com veis, crea una url per al nostre objecte a partir del nom que li hem donat a la url que serveix per visualitzar-ho.

Això ens permet no anar posant les urls a foc, sinó tenir-les definides amb un nom dins tota l'aplicació.

Si ara provau de fer anar el formulari veureu que ja funciona. Podem crear-lo i en guardar mostrar el resulta. Ja tenim dues de les quatre lletres, anem a veure les que falten.

Actualització

L'actualització és també molt senzilla. Farem servir UpdateView però a diferència de la creació a la url li hem de passar bé la clau primària de l'objecte o bé l'slug (sempres suposant que aquest sigui únic).

la url:

url(r'^tutorial/sample/update/(?P<slug>[-\w]+)/$', 
    SampleUpdateView.as_view(),
    name='tutorial_update_sample'),

pràcticament semblant a la de visualització, i ara a la part de la vista bastarà fer:

from django.views.generic.edit import UpdateView

class SampleUpdateView(UpdateView):
    model = Sample

Ara sols ens falta lligar la url per poder anar a l'edició de l'objecte, ho podem fer directament des de la barra de navegació, però millor posar-ho també a la plana web, per exemple amb un link a la plantilla de visualització per a que ens dugui a l'edició de l'objecete. Ja que hi sóm posarem també el link de creació. Molt resumidament, el nostre sample_detail.html quedaria de la forma:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>test</title>
</head>
<body>
    <h1>Sample Detail</h1>
    {{sample.slug}}<br/>
    {{sample.name}}<br/>
    {{sample.ammount}}<br/>
    {{sample.comments}}<br/>

    <a href="{% url tutorial_update_sample sample.slug %}">Update</a>|
    <a href="{% url tutorial_create_sample %}">Create</a>
</body>
</html>

Eliminació

Ja hem arribat a la darrera lletra de l'acrònim. Per a esborrar un objecte Django ens proporciona la vista DeleteView. Abans d'esborrar es convenient demanar confirmació i a la implementació de Django es té això en compte.

La url serà de la forma:

url(r'^tutorial/sample/delete/(?P<slug>[-\w]+)/$',
    SampleDeleteView.as_view(),
    name='tutorial_delete_sample'),

i a la view.py

from django.views.generic.edit import DeleteView

class SampleDeleteView(DeleteView):
    model = Sample

si volem que tot funcioni per defecte, hem de crear una plantilla construida amb el nom del model i el sufixe "_confirm_delete".

En aquesta plantilla hem de demanar la confirmitat per esborrar el registre, enviant-ho a la mateixa URL d'esborrat però fent un POST. És a dir la url amb GET presenta la confirmació i amb POST fa l'acció.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>test</title>
</head>
<body>
    <h1>Sample Delete</h1>
    {{sample.slug}}<br/>
    {{sample.name}}<br/>
    {{sample.ammount}}<br/>
    {{sample.comments}}<br/>

    <p>Are you sure?</p>

    <form action="."
            method="post" accept-charset="utf-8">
            {% csrf_token %}
        <p><input type="submit" value="Delete &rarr;"></p>
    </form>

    <p><a href="<a href="{% url tutorial_view_sample sample.slug %}">Return</a>
</body>
</html>

A l'actualització o a la creació no feia falta dir-li a Django on volíem anar, per defecte anava a la visualització de l'objecte. Ara l'estam esborrant, així que no hi ha objecte. Així que obligatòriament li hem de donar la url a la que ha d'anar en cas que l'objecte s'hagi eliminat. Ho podem enviar a una plana amb el llistat de tots els registres, però com que encara no hem vist com es fa, ara per ara l'enviarem al formulari de creació.

class SampleDeleteView(DeleteView):
    model = Sample
    success_url = reverse_lazy('tutorial_create_sample')

I ja tenim el CRUD complet. Ens falta poder visualitzar un llistat paginat d'objectes i fer algunes accions bàsiques amb formularis. Ho veurem al proper apunt.


Traducciones/Translations by apertium

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


Django class based views (IV)


Escrit per Aaloy a 21 de February , 2012 a les 11:31 p.m.

Començam a lligar les class views amb els models de dades, que això es posa cada cop més interessant.

Fins ara hem vist un ús de les class views molt genèric, és a dir, amb el que sabem podem mostrar planes webs i gestionar el treball amb formularis.

Es comú en la majoria d'aplicacions web modernes que les dades venguin d'algún lloc, d'una base de dades per exemple. Així que la gent de Django han creat tota una sèrie de mixin i vistes específices per a simplificar-ne el treball.

Mostrar un objecte

Suposem que volem mostrar les dades que tenim d'un usuari. Com sabeu el model User de Django conté les dades dels usuaris i es troba a django.contrib.auth.models.

Voldríem que en especificar-ne la clau primària a la url poder mostrar les dades de l'usuari corresponent.

És a dir, es tracta de mostrar informació que es correspon a urls d'aquest tipus

'^prefix/(?P<pk>\d+)/$'

o també

'^prefix/(?P<slug>[-w]+)/$'

Django ens proporciona la classe DetailView per fer precisament això. És a dir, per estalviar-nos la major part de la feina de cercar l'objecte que es correspon amb la clau primària o l'slug i passar-ho a la plantilla per a la seva presentació. Per si us ho demanaveu DetailView es troba a django.views.generic.detail.

Anem a veure un poc DetailView per dintre:

Aquesta classe hereta de SingelObjectTemplateResponseMixin i de BaseDetailView. La primera a la seva vegada és fila de una vella coneguda TemplateResponseMixin i en sobrescriu el mètode get_template_names per tal d'establir un nom de plantilla per defecte, que en el cas dels models és de la forma app/nom_detail.html, és a dir, per al nostre exemple, si no posam cap nom de plantilla agafarà per defecte auth/user_detail.html.

Per la seva banda BaseDetailView és filla de SingleObjectMixin i de View i implementa el mètode get i li passa al contexte de la plantilla la variable object amb el contingut de l'objecte que es correspon a la clau primària.

Com que object pot resultar un tant confús, Django també passa el mateix contingut a una variable amb el nom de l'objecte si el model té el nom posat al verbose_name o bé farà servir el que li indiquem a l'atribut context_object_name de la Classe.

SingleObjectMixin és la classe encarregada de fer el gruix de la feina, i com no, també implementa el mètode get_context_data per a poder passar més informació a la plantilla.

Així doncs, mostrar un objecte pot ser tan senzill com això:

al urls.py afegim:

url(r'^user/(?P<pk>\d+)/$', UserView.as_view(),
        name='main_user'),

i no oblidem importar UserView, que crearem dins views.py i que pot ser tan simple com:

class UserView(DetailView):
    model = User

Com que User té assignat un verbose_name='User' Django passa a la plantilla tant la variable object com la variable user i per tant en condicions normals podríem fer servir tant un nom com l'altra.

En el nostre cas, i si com jo teniu dins el context processors la línia 'django.contrib.auth.context_processors.auth' us trobareu que la variable "no funciona". Això és perquè encara que se li passi, el context processor crea també una variable anomenada 'user' amb les dades de l'usuari autenticat (si existeix), i com s'assigna just abans de generar la plantilla, el contingut que prevaleix és el del context processor.

Una cosa que em va sorprendre del SignleObjectMixin és que a més del mètode get_object implementa també get_queryset. De fet get_objectcrida a get_queryset i li aplica un filtre per clau primaria (pk) o bé per slug.

Què vol dir això, doncs que tenim un nivell extra de flexibilitat a l'hora de determinar si un objecte és presentable per pantalla o no. Basta sobreescriure el mètode get_queryset i independentment que la clau primària (o l'slug) existeixi en la base de dades, podem fer que l'objecte no es mostri. Per exemple, suposem que en el nostre exemple anterior no volem mostrar l'usuari admin.

Podem fer

class UserView(DetailView):
    model = User

    def get_queryset(self):
        query = super(UserView, self).get_queryset()
        return query.exclude(username='admin')

de manera que UserView ens mostrarà la informació de qualsevol usuari llevat de l'usuari admin, en aquest cas mostrarà un error 404.

SingleObjectMixin com hem dit, pot tornar-nos un objecte a partir de la seva clau primària o bé a partir de l'slug. Normalment en farem servir un o altre. L'ordre de prioritat és primer el pk i si aquest està buid, es mirar l'slug.

Al proper post farem una ullada a les facilitats que ens donen les generic class views per a gestionar els manteniments i veurem com es pot fer un cicle CRUD.


Traducciones/Translations by apertium

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


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


Django class based views (I)


Escrit per Aaloy a 19 de February , 2012 a les 8:26 p.m.

Class based views

Amb Django 1.3 s'introdueixen les "Class based views", una funcionalitat que ens permet modelar les nostres vistes com a classes i que a més intenta solucionar el no tenir que escriure sempre el mateix tipus de codi quan mostram una plana web o fem un manteniment lligat a un model de dades.

Les class based views ens permeten un nivell més alt de reutilització del nostre codi i a més ens permeten de mantenir-ne la cohesió. Fins ara o bé teníem que anar creant les funcions dins la mateixa vista, dins la mateixa funció o tirar de mòduls independents.

Amb les class based views pode anar creant funcions per a la nostra vista, de manera que després sigui més bo de fer reaprofitar-les extenent la classe.

En poques paraules, les class based views requereixen un temps d'adaptació i alguns diuen que aprendre un nou DSL (Domain Specific Language). Potser sí, però s'ho paga.

Abans de començar

Encara que podem utilitzar les nostres pròpies class based views, Django ens dóna ja fetes les més freqüents i un conjunt de classes que podem fer servir en cas que cap ens encaixi.

És en aquestes classes on hi ha gran part de la potència de les class based views. Per a fer-les servir hem d'entendre el concepte de Mixin, ja que això són aquestes classes: Python mixins.

Què és un mixin?

Un mixin no és res més que una classe que no està concebuda per tenir entitat per sí mateixa, sinó per extendra la funcionalitat d'altres classes fent servir l'herència múltiple de Python.

Un mixin, per tant, afegeix funcionalitat a les classes. Podem crear Mixins com si de mòduls Python es tractàs i aplicar-los a les nostres classes per dotar-les de més funcionalitat.

Hem d'entendre també com funciona l'herència múltiple en Python, de manera resumida: s'executarà el primer mètode que es trobi anant d'esquerra cap a dreta en la definició de les classes. És a dir, si tenim:

class Test(OneMixin, AnotherMixin):
    pass

a l'hora d'executar un mètode, Python primer cercarà a OneMixin i si no ho troba anirà a AnotherMixin. Així doncs, al tanto que l'ordre en que posam les classes és important.

A diferència dels mòduls els mixins tenen accés a la instància self de la clases, i per tant poden fer servir informació associada a l'objecte i altres mètodes.

La classe View

Aquesta classe és el bessó de tot el muntatge, i si cap de les funcionalitats que ens proporciona Django ens serveix gairebé segur que començarem a crear la nostra pròpia extenent aquesta classe.

Anem a fer un exemple molt senzill, suposem que volem mostrar una plana web, que anomenarem index.html i que posaré dins la carpeta main. Com que començam amb això de les generic class views, anam llegit i ens n'adonam que la classe View el que fa és capturar els noms dels mètodes http i executar-ne una funció associada amb el matix nom.

Ja està, ens deim, el que hem de fer es crear una classe que hereti de View i escriure'n un mètode que respongui a la creidada GET i que ens renderitzi la plana. Dit i fet, cream l'aplicació main i la registram al settins.py i anam per feina:

Al mòdul views.py

from django.shortcuts import render_to_response
from django.views.generic.base import View
from django.template import RequestContext

class TestView(View):
    def get(self, request, *args, **kwargs):
        return render_to_response('main/index.html', {},
            context_instance=RequestContext(self.request) )

i a urls.py de l'aplicació main

from django.conf.urls.defaults import *
from views import TestView

urlpatterns = patterns('',
    url(r'^test2/$', TestView.as_view(), name="test-class-view"),
    )

Executam i efectivament això funciona. Ens mostra la plana. Però la cosa no ens acaba de convèncer, no?, ja que si fem una ullada a la manera anterior de fer les coses, això ben bé es podria fer com:

from django.shortcuts import render_to_response
from django.template import RequestContext

def test(request):
    return render_to_response('main/index.html', {},
        context_instance=RequestContext(request) )

hem escrit fins i tot més, on està el guany, doncs? La resposta està en la reusabilitat.

Aquest patró de mostrar planes web senzilles es repeteix molt en les aplicacions web, de manera que la gent de Django ja ha creat una classe que hereta de Views i que fa precissament això i d'una manera més genèrica, la classe TemplateView

Aquesta classe hereta de TemplateResponseMixin i de View. TemplateResponseMixin és un mixin que té dos mètodes: render_to_response i get_templates_names i dos atributs: template_name i response_class. El que ens interessa ara és que TemplateResponseMixin ens permet afegir a la nostra classe la renderització de la plantilla que passem dins template_name o bé que tornem a partir de la sobreescriptura de get_templates_names

Si fem servir TemplateView el nostre codi queda com:

from django.views.generic.base import TemplateView

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

i a urls.py

from django.conf.urls.defaults import *
from views import IndexView

urlpatterns = patterns('',
    url(r'^$', IndexView.as_view(), name='main_index'),
    )

Fins i tot podríem prescindir del codi de view.py i podríem fer:

from django.conf.urls.defaults import *
from django.views.generic.base import TemplateView

urlpatterns = patterns('',
    url(r'^$', TemplateView.as_view(template_name='main/index.html'), 
        name='main_index'),
    )

Com podeu veure l'estalvi en línies de codi comença a ser notable.

L'important, a més de veure com es pot extendre la funcionalitat de Views és que vegem què senzill és separar la funcionalitat lligada al GET de la funcionalitat lligada al POST, al PUT, o a qualsevol cridada HTTP. Sols hem de crear una funció amb aquest nom i Django la cridarà sempre que la nostra classe hereti de View.

Suposem doncs que el que volem ara és mostrar una plana diferent si algú ens intenta fer un POST contra la nostra plana. Django té un mecanisme per protegir-nos d'això és el CSRF o Cross Site Request Forgery protection, ara el que voldriem es desactivar aquest mecanisme per la nostra classe i mostrar una plana diferent d'avís.

Amb el mètode classis hauríem de fer un

if request.method=='POST':
    #fes alguna cosa
else:
    #fes una altra cosa

amb les Class Based Views la cosa queda molt més elegant:

class TestView(View):
    def get(self, request, *args, **kwargs):
        return render_to_response('main/index.html', {},
            context_instance=RequestContext(self.request) )

    def post(self, request, *args, **kwargs):
        return render_to_response('no_post_allowed.html')

Això tal com està no funcionaria, ja que tenim pel mig la protecció de Django.

Per saltar-nos aquesta protecció li hem de dir a la vista que no la volem i per fer-ho hem de sobreescriure el mètode dispatch que se n'encarrega de la fontaneria de verificar quin mètode http ens ve i la funció que hem de cridar.

from django.views.generic.base import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

class TestView(View): 
    def get(self, request, *args, **kwargs):
        return render_to_response('main/index.html', {},
            context_instance=RequestContext(self.request) )

    def post(self, request, *args, **kwargs):
        return render_to_response('no_post_allowed.html')

    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(TestView, self).dispatch(*args, **kwargs)

Hem vist fins ara com fer servir la classe View i la classe TemplateView, hi tornarem en els propers apunts, estau atents.


Traducciones/Translations by apertium

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


Dajaxaproject, segona part


Escrit per Aaloy a 12 de February , 2012 a les 2:26 p.m.

Abans d'escriure aquest apunt de m'he adonat que era el post amb l'identificador 500. Un nombre rodó i que potser mereixeria algun tipus de celebració, però tanmateix no és l'apunt que fa 500, ja que en les proves del codi del blog, segurament vaig afegir i esborrar apunts que han fet anar cap endavant el comptador, realment encara falten prop d'una trentena d'apunts per a arribar al que fa 500, així que deixaré el cava a la gelera.

Hi va haver una proposta de celebrar-ho fent un regal, però pareix que la meva proposta de regalar una capsa d'un Nexus (tot i que comptava amb un possible patrocionador) no ha arribat a cuallar :-P

Així doncs el que faré és reprende l'apunt sobre el dajaxproject, i em centraré en l'altra component, anomenat Dajax.

Dajax

Ja vàrem veure com dajaxice ens deixava fer cridades AJAX cap a la nostra aplicació Django i organitzar-les. Dajax va més enllà in ens permet interactuar amb la interfície d'usuari des de codi Python. Fet servir amb mesura i seny ens pot ajudar també a fer molt més legible el nostre codi i a organtizar millor les accions derivades d'una cridada AJAX dins el mateix codi que les gestiona. No us penseu però que es tracta de quelcom com Pyjamas sinó que són més bé un conjunt de primitives que ens permetran agilitar la feina.

Per començar a jugar-hi necessitareu haver llegit el meu apunt anterior (o la documentació del projecte) ja que Dajax fa servir la seva llibreria germana dajaxice i també tenir instal·lat algun dels frameworks javascript suportats, en el meu cas faré servir jQuery.

Recepta

A la part de servidor

  1. Instal·lau django-dajax i posau dajax com a aplicació al settings.py.

  2. Posau from dajax.core.Dajax import Dajax a l'arxiu ajax.py de la vostra aplicació, junt amb from dajaxice.decorators import dajaxice_register que ja havíem vist abans per a registrar la petició.

  3. A la funció AJAX instanciarem la classe, dajax=Dajax()

  4. Farem el que tinguem que fer dins la funció AJAX i després anirem executant els mètodes de dajax que ens interessin per a interaccionar amb la part HTML de l'aplicació.

  5. Finalment retornarem el json amb return dajax.json()

A la part de client

Hem de afegir l'arxiu javascript que correspongui segons la llibreria que volguem fer servir. En el nostre cas jquery.dajax.core.js que es troba a django-dajax/scr/. En el meu cas la cosa queda com:

{% dajaxice_js_import %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="{{STATIC_URL}}/jquery.dajax.core.js"></script>

En executar-se l'event que ens interessi (un clic normalment) cridarem la función AJAX que hem registrat de manera semblant a com ho feiem a l'exemple de Dajaxice, però ara a la funció li passarem com a paràmetre Dajax.process i un diccionari java de valors opcional, si la funció té paràmetres.

Important Un ús molt habitual de Dajax es dona en combinació amb els formularis Django, en aquest cas el paràmetre de les dades del formulari. Per a que Django les pugui interpretar bé com a provinents d'un formulari hem de serialitzar-les, si fem servir jQuery això necessita d'una llibreria addicional anomenada serializeobject.

Què es pot fer?

Les funcions que ens proporciona Dajax ens permeten tant manipular l'arbre DOM de la plana web com cridar a funcions javascript ja que tinguem fetes.

*alert(message) Com el seu nou indica hi haureu endivinat, mostra una alterta javascript amb el missatge que li indiquem.

  • assign(selector, attribute, valor) Donat un selector (id, classe, tipus, ...) i un atribut li asigna el valor el valor que li passam al paràmetre a tots els elements que casin amb el selector. Seria l'equivalent a fer amb jQuery per exemple $('.test').attr('title', 'hola') però enlloc de aplicar-ho sols al primer element com fa jQuery ho aplicaria a tots els elements que tinguin assignada la classe test.

  • add_css_class(selector,value). Assigna tots els elements que casin amb el selector la classe css especificada a paràmetre value. value pot ser una cadena o bé una llista de cadenes, en cas de ser una llista s'assignaran totes les classes de la llista.

  • remove_css_class(selector,value). Funció complementaria de l'anterior, és a dir, enlloc de posar lleva.

  • append(selector,attribute,value)afegeix a tots els elements que casin amb el seletor l'atribut donat amb el valor indicat avalue`.

  • prepend(selector,attribute,value) Afegeix a l'inici a tots els elements que casin amb el selector l'atribut especificat amb el valor que li passam com a paràmetre.

  • clear(selector,attribute). Netja l'atribut especificat de tots els elements que casin amb el selector.

  • redirect(url,delay=0). Redirecciona la plana cap la nova url després del temps especificat en el paràmetre delay.

  • script(code). Executa el codi javascript especificat en el navegador. Per exemple, podem executar l'alert de l'exemple anterior. Hem d'anar un poc alerta amb aquesta funció i evitar fer-ne un mal ús posant-hi tot el codi. Si hem de fer crides, millor posar-les dins el seu corresponent arxiu javascript i fer tan sols script(la_funcio()).

  • remove(selector). Elimina tots els elements que casen amb el selector especificat.

  • add_data(data,callback_function). Executa la funció que li especificam en el segon paràmetre amb les dades que hem posat al primer paràmetre. Com el cas d'script alerta amb abusar-ne.

Algunes recomanacions

Django té un sistema de plantilles molt potent, es poden fer servir junt amb assign i remove per a posar els continguts que ens interessin. Vull dir que no fa falta que una plantilla estigui lligada a un view sinó que es pot fer servir directament. Mirau-ne l'API.

Podem generar contingut HTML (o js) directament o fer servir una plantilla a un fitxer i utilitzar el render_to_string.

Tampoc estam limitats a fer servir el sistema de plantilles de Django per a generar l'HTML segons el que es tengui que tornar potser un sistema com Mako us va millor. Personalment sóc partidari de no mesclar massa.

Errors típics

Sovint et pots passar una bona estona demanant-te perquè no funciona la teva cridada AJAX, així que anem a fer una llista de recomanacions:

  1. Reinicia la consola de desenvolupament. A vegades la nova función no es registra bé i no es torna a generar el codi javascript. Si això passa, el símptoma és que tens un error dient que no es troba la funció.

  2. Hi ha un error que diu que no es troba Dajax al javascript. Típic de quan t'has oblidat de incloure la llibreria específica pel frameworks que fas servir.

  3. Es fa la cridad però no s'executa res. Doncs que t'has deixat el retorn a la funció AJAX. Recorda que normalment hi haurà un return dajax.json() o si no les cridades no s'efectuaran.

  4. Django no és capaç d'interpretar el que li enviam al formulari. Ens hem deixat la llibreria serializeobject

  5. Teníem events lligats a un element i ara no responen. Típic de quan hem generat nou contingut. Els events s'han de tornar a lligar amb els nous continguts encara que tenguéssin el mateix identificador. Potser fer servir .live() o .delegate() de jQuery ajudaria, però ara per ara jo el que he fet és tornar-los a lligar posant els elements a una funció d'inicialització i tornar-la a cridar amb script.


Traducciones/Translations by apertium

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