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 →"></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 →"></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
-
Instal·lau django-dajax i posau
dajaxcom a aplicació alsettings.py. -
Posau
from dajax.core.Dajax import Dajaxa l'arxiu ajax.py de la vostra aplicació, junt ambfrom dajaxice.decorators import dajaxice_registerque ja havíem vist abans per a registrar la petició. -
A la funció AJAX instanciarem la classe,
dajax=Dajax() -
Farem el que tinguem que fer dins la funció AJAX i després anirem executant els mètodes de
dajaxque ens interessin per a interaccionar amb la part HTML de l'aplicació. -
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 classetest. -
add_css_class(selector,value). Assigna tots els elements que casin amb el selector la classe css especificada a paràmetrevalue.valuepot 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àmetredelay. -
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 solsscript(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'scriptalerta 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:
-
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ó.
-
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.
-
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. -
Django no és capaç d'interpretar el que li enviam al formulari. Ens hem deixat la llibreria
serializeobject -
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
