Django: utilizzo avanzato e nuove funzionalità

download Django: utilizzo avanzato e nuove funzionalità

If you can't read please download the document

description

Django è uno dei framework web più apprezzati e utilizzati dalla comunità Python (e non solo).I suoi punti di forza sono rappresentati dal suo utilizzo rapido e intuitivo, l'ottima documentazione e una larga comunità di sviluppatori ed utilizzatori.A più di un anno di distanza dal rilascio della versione 0.96 le feature e i miglioramenti introdotti sono stati molti.La presentazione mostra alcune di queste novità e l'utilizzo avanzato di alcuni componenti del framework.

Transcript of Django: utilizzo avanzato e nuove funzionalità

  • 1. Django Utilizzo avanzato e nuove funzionalit

2. Perch utilizzare django?

  • Tempi di sviluppo ridotti
  • Scalabilit e performance
  • Ottima documentazione, comunit attiva e partecipe
  • Motore di templating semplice e adatto ai designer di pagine HTML
  • URL puliti e SEO-friendly
  • Python powered!

3. Perch utilizzare la versione trunk?

  • 1 anno dall'ultima release stabile (0.9.6)
  • Supporto unicode
  • Cambiamenti dell' API
  • Nuove funzionalit

4. Utilizzo della versione trunk

  • Installazione da sorgenti
  • $ cd /path/to/packages
  • $ svn cohttp://code.djangoproject.com/svn/django/trunkDjango
  • $ python setup.py install
  • Utilizzo senza installazione
  • $ export PATH=$PATH:/path/to/Django/bin
  • $ export PYTHONPATH=$PYTHONPATH:/path/to/packages/Django

5. Supporto unicode: gestione delle stringhe

  • Django assume che tutte le bytestring siano inUTF-8 , in caso contrario l'eccezioneUnicodeDecodeErrorverr sollevata
  • La variabile di configurazioneDEFAULT_CHARSETviene utilizzata solo per il rendering dei template e dei messaggi e-mail
  • Le stringhe di traduzione lazy possono essere convertite direttamente in stringhe unicode
      • http://www.djangoproject.com/documentation/i18n/#lazy-translation

6. Supporto unicode: funzioni di conversione

  • django.utils.encoding.smart_unicode
    • Converte una qualsiasi stringa in unicode
  • django.utils.encoding.force_unicode
    • Identica a smart_unicode, con la differenza che non preserva le stringhe di traduzione lazy, forzandone la traduzione e la conversione in unicode

7. Supporto unicode: URI e IRI

  • Gli URL di tipo URI utilizzano solo caratteri ASCII.
  • La funzioneiri_to_uripermette di costruire URL di tipo IRI (RFC 3987)
      • http://www.ietf.org/rfc/rfc3987.txt
  • >>> from django.utils.encoding import iri_to_uri
  • >>> iri_to_uri(u'/locations/Rennes-le-Chteau')
  • '/locations/Rennes-le-Ch%C3%A2teau'

8. Supporto unicode: database

  • Assicurarsi che il proprio database sia configurato per utilizzare l'encoding appropriato ( UTF-8, UTF-16 )
  • Il database backend si occuper di convertire automaticamente le stringhe unicode nell'encoding utilizzato dal database, e viceversa
  • possibile passare stringhe unicode e bytestringUTF-8ai metodi utilizzati per filtrare gli oggettiQuerySet
  • >>> Place.objects.filter(name__contains=u'Chteau')

9. Supporto unicode: modelli

  • Le stringhe ottenute dai campi testuali dei modelli sono sempre di tipo unicode
  • Nel caso il vostro modello contenga un metodo__str__()pu essere necessario implementare anche il metodo __unicode__()
  • Se necessario, utilizzare la funzioneiri_to_uri()nel metodoget_absolute_url()

10. Supporto unicode: template

  • I template possono essere salvati sul filesystem utilizzando un charset diverso daUTF-8 , in tal caso necessario definire la variabile di configurazioneFILE_CHARSET
  • consigliato l'utilizzo delle funzioneforce_unicodenella creazione di template tag

11. Escape automatico dei template

  • Qualsiasi variabile inserita nel contesto di un template viene convertita automaticamente utilizzandodjango.utils.html.escape
  • stata introdotta con lo scopo di prevenire attacchi di tipoXSS(Cross-site scripting)
  • Non possibile disabilitare tale funzionalit globalmente

12. Escape automatico dei template

  • possibile disabilitare l'escape automatico direttamente nei template {% autoescape off %} ... {{ entry.body }} ... {% endautoescape %} {{ entry.body|safe }}

13. Escape automatico dei template

  • inoltre possibile disabilitare l'escape automatico nei template filter @register.filter def safefilter(value): # do something with value... return value safefilter.is_safe = True
  • template.Contextaccetta l'argomentoautoescapetemplate.Context({'foo': bar}, autoescape=False)

14. Modelli

  • La classeLazyDate() stata rimossa. possibile utilizzare una funzione per l'argomentodefault from datetime import datetime from django.db import models from django.contrib.auth.models import User class Place(models.Model): name = models.CharField(max_length=100) description = models.TextField(blank=True) created = models.DateField(default=datetime.now) location = models.ForeignKey(Location) author = models.ForeignKey(User)
  • L'utilizzo dell'attributomaxlength deprecato ed stato sostituito damax_length

15. Modelli

  • I campi di tipoFloatFieldutilizzavanofloatper rappresentare numeri decimali a precisione fissa
  • stato introdotto il campoDecimalField , che utilizza il tipo di datodecimal.Decimalper mantenere la precisione latitude = models.DecimalField(max_digits=8,decimal_places=6)
  • FloatFieldnon accetta pi gli argomenti precedentemente utilizzati

16. Modelli

  • stato recentemente aggiunto il supporto all'ereditariet delle classiModel class Place(models.Model): name = models.CharField(max_length=100) description = models.TextField(blank=True) created = models.DateField(default=datetime.now) location = models.ForeignKey(Location) author = models.ForeignKey(User) class Restaurant(Place): has_bistecca_fiorentina = models.BooleanField()
  • http://www.djangoproject.com/documentation/model-api/#model-inheritance http://code.djangoproject.com/wiki/QuerysetRefactorBranch

17. URL patterns

  • ora possibile utilizzare la funzioneurl()per assegnare un nome a un determinato URL pattern from django.conf.urls.defaults import * urlpatterns = patterns('pycon2.places.views',url(r'^add/$', 'place_add', name='place_add'), url(r'^(?Pd+)/edit/$', 'place_edit', name='place_edit'), url(r'^(?Pd+)/delete/$','place_delete', name='place_delete') )

18. URL patterns: permalink

  • Utilizzo nel metodoget_absolute_url()di un modello: from django.db.models import permalink def get_absolute_url(self): return ('place_detail', [str(self.id)]) get_absolute_url = permalink(get_absolute_url)

19. URL patterns: url template tag

  • Add place Edit {{ place }} Delete {{ place }}

20. MEDIA_URL context processor

  • La variabile di configurazioneMEDIA_URL ora disponibile per poter essere utilizzata nei template

21. URL patterns: urlresolvers

  • >>> from django.core.urlresolvers import reverse >>> reverse('place_add') '/places/add/' >>> reverse('place_edit', args=[1]) '/places/1/edit/' >>> reverse('place_delete', args=[1]) '/places/1/delete/'

22. newforms

  • Introdotta nella release 0.9.6 con lo scopo di sostituire la vecchia libreria di gestione dei form
  • Permette di semplificare la creazione dei form, la validazione dei dati ricevuti, l'utilizzo e la creazione di widget HTML personalizzati
  • django.forms stata copiata indjango.oldforms
  • Nella prossima release stabiledjango.newformsverr rinominata indjango.forms

23. newforms

  • Form
    • Contiene i campi, riceve i dati, si occupa della loro validazione e del rendering in HTML
  • Field
    • Rappresenta un campo e si occupa della sua validazione
  • Widget
    • Rendering di un campo in HTML

24. newforms

    • # pycon2.contacts.forms
    • from django import newforms as forms
    • class ContactForm(forms.Form):
    • name = forms.CharField(required=False, max_length=50)email = forms.EmailField()comment = forms.CharField(widget=forms.Textarea)
    • Gli argomentimax_lengtherequiredvengono utilizzati per la validazione del campo name
    • L'argomentowidgetviene utilizzato per definire il tipo di widget utilizzato per l'output HTML

25. newforms

  • I dati ricevuti vengono passati come primo argomento del costruttore della classe
  • possibile verificare la validit dei dati ricevutiutilizzando il metodois_valid()
  • L'attributoerrors un dizionario contenente gli errori di validazione
  • Nella release 0.9.6 i datidel form, normalizzati a seconda dei tipi di campo, erano disponibili nell'attributoclean_data , che stato rinominato incleaned_data

26. newforms

  • >>> form = ContactForm() >>> form.is_bound False >>> form = ContactForm({'email': '[email protected] '}) >>> form.is_bound True >>> form.is_valid() False >>> form.errors {'comment': [u'This field is required.']}

27. newforms

  • Per gestire gli upload possibile passarerequest.FILEScome secondo argomento nel costruttore della classe
  • Il metodois_multipart()ritornaTruese la classe contiene campi di tipoFileField
  • possibile accedere al nome e al contenuto del file attraversocleaned_data form.cleaned_data['fieldname'].content form.cleaned_data['fieldname'].filename

28. newforms: esempio di utilizzo

    • from django.template import RequestContext
    • from django.template.loader import render_to_string
    • from django.shortcuts import render_to_response
    • from django.core.mail import mail_admins
    • from pycon2.contacts.forms import ContactForm
    • def contact(request, template='contacts/contact.html', email_template='contacts/contact_email.txt'):
    • if request.method == 'POST':
    • form = ContactForm(request.POST)
    • if form.is_valid():
    • message = render_to_string(email_template,
    • form.cleaned_data)
    • mail_admins('contact request', message)
    • else:
    • form = ContactForm()
    • return render_to_response(template, {'form': form},context_instance=RequestContext(request))

29. Field e Widget personalizzati: CaptchaField

    • # pycon2.contacts.forms
    • from django import newforms as forms
    • from pycon2.utils.captcha.fields import CaptchaField
    • class ContactForm(forms.Form):
    • name = forms.CharField(required=False, max_length=50)
    • email = forms.EmailField()
    • comment = forms.CharField(widget=forms.Textarea)
    • captcha = CaptchaField()

30. CaptchaWidget

    • # pycon2.utils.captcha.fields
    • from django.newforms.util import flatatt
    • from django.utils.safestring import mark_safe
    • CAPTCHA_IMAGE = ''''''
    • class CaptchaWidget(forms.TextInput):
    • def render(self, name, value, attrs=None):
    • captcha_url = reverse('captcha')
    • image = CAPTCHA_IMAGE % captcha_url
    • if value is None: value = ''
    • final_attrs = self.build_attrs(attrs,type=self.input_type,name=name)
    • attrs = flatatt(final_attrs)
    • if value != '':
    • final_attrs['value'] = force_unicode(value)
    • return mark_safe(u'%s' % (image, attrs))

31. CaptchaWidget: funzioni utilizzate

  • django.newforms.util.flatatt
    • Converte un dizionario in una stringa contenente gli attributi utilizzabili in un elemento HTML. I valori del dizionario vengono convertiti utilizzando la funzionedjango.utils.html.escape
  • django.utils.safestring.mark_safe
    • Funzione utilizzata per definire che la stringa passata come argomento sicura per poter essere utilizzata nell'output HTML

32. CaptchaField

    • # pycon2.utils.captcha.fields
    • from django.utils.translation import ugettext as _
    • from pycon2.utils.captcha.middleware import
    • get_current_captcha
    • class CaptchaField(forms.CharField):
    • widget = CaptchaWidget
    • def clean(self, value):
    • captcha = get_current_captcha()
    • if not captcha:
    • raise forms.ValidationError(_('You must enable cookies.'))
    • if value != captcha:
    • raise forms.ValidationError(_('Incorrect! Try again.'))
    • return value

33. Captcha middleware

    • # pycon2.utils.captcha.middleware
    • try:
    • from threading import local
    • except ImportError:
    • from django.utils._threading_local import local
    • _thread_locals = local()
    • def get_current_captcha():
    • return getattr(_thread_locals, 'captcha', None)
    • class CaptchaMiddleware(object):
    • def process_request(self, request):
    • captcha = None session = getattr(request, 'session', None) if session: captcha = session.get('captcha')
    • _thread_locals.captcha = captcha

34. Captcha view

    • # pycon2.utils.captcha.view
    • import Captcha
    • import cStringIO
    • from Captcha.Visual.Tests import PseudoGimpy
    • from django.http import HttpResponse
    • def captcha(request):'''Visualizza un immagine captcha utilizzando PyCaptcha
    • '''
    • g = PseudoGimpy()
    • s = cStringIO.StringIO()
    • i = g.render()
    • i.save(s, 'JPEG')
    • request.session['captcha'] = g.solutions[0]
    • return HttpResponse(s.getvalue(), 'image/jpeg')

35. Form preview

  • Viene fornita un'applicazione per poter visualizzare l'anteprima di una classe form
    • Il form viene visualizzato in formato HTML su una pagina web
    • I dati del form vengono validati
      • Se il form non valido, viene visualizzato con gli errori di validazione
      • Se il form valido, viene visualizzato per conferma
    • I dati del form di conferma vengono ricevuti da un hook definito dall'utente

36. Form preview # pycon2.contacts.preview import pprint from django.http import HttpResponse from django.contrib.formtools.preview importFormPreview from pycon2.contacts.forms import ContactForm class ContactFormPreview(FormPreview): def done(self, request, cleaned_data): # do something with cleaned_data... output = pprint.pformat(cleaned_data) return HttpResponse(output, mimetype='text/plain') contact = ContactFormPreview(ContactForm) 37. Form preview

  • Per visualizzare l'anteprima sufficiente aggiungere un pattern nella propria configurazione degli URL ('^preview/contact/$', 'pycon2.contacts.preview.contact'),

38. Form wizard

  • stata introdotta un applicazione che permette di creare form suddivisi su pi pagine
  • L'applicazione si occupa di mantenere lo stato dei dati inseriti nei form fra una pagina e l'altra
  • Le istanze delle classi form vengono passate al metododone()
  • possibile specificare i template da utilizzare definendo il metodoget_template()

39. Form wizard # pycon2.contacts.forms from django import newforms as forms from django.http import HttpResponseRedirect from django.contrib.formtools.wizard import FormWizard from django.core.urlresolvers import reverse class PollOne(forms.Form): name = forms.CharField() email = forms.EmailField() job_position = forms.CharField() LANGUAGE_CHOICES = ( ('python', 'Python'), ('ruby', 'Ruby'),('perl', 'Perl'), ('php', 'PHP'), ) class PollTwo(forms.Form): languages = forms.MultipleChoiceField(choices=LANGUAGE_CHOICES) preferred_language = forms.ChoiceField(choices=LANGUAGE_CHOICES) 40. Form wizard # pycon2.contacts.forms class PollWizard(FormWizard): def done(self, request, form_list): form_data = [form.cleaned_data for form in form_list] # do something with form_data return HttpResponseRedirect(reverse('poll_done')) def get_template(self, step): '''Override default template forms/wizard.html''' return ['contacts/poll_%s.html' % step, 'contacts/poll.html'] # pycon2.contacts.urls from django.conf.urls.defaults import * from pycon2.contacts.forms import PollOne, PollTwo, PollWizard urlpatterns = patterns('', url(r'^poll/$', PollWizard([PollOne, PollTwo]), name='poll'), url(r'^poll/done/$', 'django.views.generic.simple.direct_to_template', {'template': 'contacts/poll_done.html'}, name='poll_done') ) 41. Form wizard {# templates/contacts/poll.html #} {% extends 'base_site.html' %} {% block title %} Poll - step {{ step }} of {{ step_count }} {% endblock %} {% block content %} {{ form.as_p }} {{ previous_fields|safe }}

{% endblock %} 42. ModelForm

  • Permette di creare una classeFormda un modello
  • Sostituisce le funzioni deprecateform_for_model()eform_for_instance()
  • Permette di modificare con facilit i campi generati automaticamente

43. ModelForm

  • # pycon2.places.forms
  • from django import newforms as forms
  • from pycon2.places.models import Place
  • class PlaceForm(forms.ModelForm):
  • class Meta:
  • model = Place
  • exclude = ('created', 'author')
  • La classe internaMetaviene utilizzata per definire
    • il modello da utilizzare
    • i campi da escludere

44. ModelForm

  • Il costruttore della classe accetta l'argomentoinstanceper definire l'istanza del modello da utilizzare
  • Il metodosave()ritorna l'istanza del modello
    • accetta il parametrocommit : se il suo valore Falseil metodo restituisce un istanza non salvata
    • in tal caso il metodosave_m2m()deve essere utilizzato per poter salvare i dati delle relazioni many-to-many

45. ModelForm: esempio di utilizzo # pycon2.places.views @login_required def place_add(request, template='places/place_add.html'): if request.method == 'POST': form = PlaceForm(request.POST) if form.is_valid(): place = form.save(commit=False) place.author = request.user place.save() return HttpResponseRedirect(place.get_absolute_url()) else: form = PlaceForm() return render_to_response(template, {'form': form}, context_instance=RequestContext(request)) 46. ModelForm: esempio di utilizzo # pycon2.places.views @login_required def place_edit(request, place_id,template='places/place_edit.html'): place = get_object_or_404(Place, pk=place_id) if request.method == 'POST': form = PlaceForm(request.POST, instance=place) if form.is_valid(): form.save() return HttpResponseRedirect(place.get_absolute_url()) else: form = PlaceForm(instance=place) return render_to_response(template, {'form': form, 'place': place}, context_instance=RequestContext(request)) 47. newforms-admin

  • Utilizza la librerianewforms , eliminando le dipendenze conoldforms
  • Disaccoppia le funzionalit dell'interfaccia di amministrazione dai modelli
  • Permette di personalizzare alcuni aspetti dell'interfaccia di amministrazione, fra cui:
    • Definire widget personalizzati per i campi e modificarne gli attributi
    • Modificare i permessi sui singoli oggetti

48. newforms-admin: installazione

  • Scaricare il branch e modificare la variabile d'ambientePYTHONPATH http://code.djangoproject.com/svn/django/branches/newforms-admin/
  • Modificare la propria configurazione degli URL from django.conf.urls.defaults import * from django.contrib import admin urlpatterns = patterns('', (r'^admin/(.*)', admin.site.root), )

49. newforms-admin: esempio di utilizzo # pycon2.places.models from django.contrib import admin class Place(models.Model): name = models.CharField(max_length=100) description = models.TextField(blank=True) created = models.DateField(default=datetime.now) location = models.ForeignKey(Location) author = models.ForeignKey(User) class Location(models.Model): name = models.CharField(max_length=100) latitude = models.DecimalField(max_digits=8, decimal_places=6) longitude = models.DecimalField(max_digits=8, decimal_places=6) # register models into admin site admin.site.register(Place) admin.site.register(Location) 50. newforms-admin: esempio di utilizzo class PlaceOptions(admin.ModelAdmin): list_display = ('name', 'location', 'author') search_fields = ('name', 'description') def has_change_permission(self, request, obj=None): if obj: if request.user != obj.author: return False return super(PlaceOptions, self).has_change_permission(request, obj) class PlaceInline(admin.TabularInline) model = Place extra = 5 class LocationOptions(admin.ModelAdmin): inlines = [PlaceInline] # register models into admin site admin.site.register(Place, PlaceOptions) admin.site.register(Location, LocationOptions) 51. Contenttypes

  • Applicazione basata sul modelloContentType , che contiene i seguenti campi
    • app_label(nome dell'applicazione)
    • model(nome del modello)
    • name( verbose_namedi un modello)
  • Permette di creare delle relazioni generiche fral' istanza di un modello e diverse altre istanze
  • Viene utilizzata in diverse applicazioni fra cui django.contrib.comments, django-tagging, django-voting etc.

52. Contenttypes

    • >>> from django.contrib.contenttypes.models import ContentType
    • >>> place_type = ContentType.objects.get(app_label='places',
    • model='place')
    • >>> place_type
    • >>> place_type.model_class()
    • >>> from pycon2.places.models import Location
    • >>> ContentType.objects.get_for_model(Location)

53. Contenttypes: ObjectCounter

    • # pycon2.counter.models
    • from django.db import models
    • from django.contrib.contenttypes.models import ContentType
    • from django.contrib.contenttypes import generic
    • from django.contrib.auth.models import User
    • from pycon2.counter.managers import ObjectCounterManager
    • class ObjectCounter(models.Model):
    • content_type = models.ForeignKey(ContentType)
    • object_id = models.PositiveIntegerField()
    • content_object = generic.GenericForeignKey('content_type', 'object_id')
    • visited = models.DateTimeField(auto_now_add=True)
    • user = models.ForeignKey(User, blank=True, null=True)
    • objects = ObjectCounterManager()
    • def __unicode__(self):
    • return u'%s: %d' % (self.content_type.name, self.object_id)

54. Contenttypes: ObjectCounterManager

    • # pycon2.counter.managers
    • from django.db import models, connection
    • from django.contrib.contenttypes.models import ContentType
    • class ObjectCounterManager(models.Manager):
    • def count_object(self, obj, user=None):
    • counter = self.model()
    • counter.content_object = obj
    • if user: counter.user = user
    • counter.save()
    • def most_visited_for_model(self, model, num=10):
    • '''Inspired by http://www.djangosnippets.org/snippets/108/'''
    • content_type = ContentType.objects.get_for_model(model)
    • primary_table = model._meta.db_table
    • secondary_table = self.model()._meta.db_table
    • query = """SELECT p.id AS obj_id, COUNT(*) AS score
    • FROM %s p INNER JOIN %s s ON (p.id = s.object_id)
    • WHERE s.content_type_id = %%s GROUP BY obj_id
    • ORDER BY score DESC""" % (primary_table, secondary_table)
    • cursor = connection.cursor()
    • cursor.execute(query, [content_type.id])
    • object_ids = [row[0] for row in cursor.fetchall()[:num]]
    • object_dict = model._default_manager.in_bulk(object_ids)
    • return [object_dict[object_id] for object_id in object_ids]

55. Contenttypes: utilizzo di ObjectCounter

    • >>> from django.db import models
    • >>> from pycon2.counter.models import ObjectCounter
    • >>> from pycon2.members.models import Profile
    • >>> profile1 = Profile.objects.get(pk=1)
    • >>> profile2 = Profile.objects.get(pk=2)
    • >>> profile3 = Profile.objects.get(pk=3)
    • >>> ObjectCounter.objects.count_object(profile1)
    • >>> ObjectCounter.objects.count_object(profile1)
    • >>> ObjectCounter.objects.count_object(profile1)
    • >>> ObjectCounter.objects.count_object(profile2)
    • >>> ObjectCounter.objects.count_object(profile2)
    • >>> ObjectCounter.objects.count_object(profile3)
    • >>> ObjectCounter.objects.most_visited_for_model(Profile)
    • [, , ]

56. Signals

  • Django utilizza il dispatching di eventi per la gestione di determinate funzionalit
  • possibile collegare le proprie funzioni ai segnali utilizzati internamente dal framework
  • Tali funzionalit sono offerte da PyDispatcher, che stato incluso a partiredalla versione 0.9.5

57. Signals

  • I seguenti segnali sono utilizzati internamente nei modelli e sono definiti indjango.db.models.signals
    • pre_init
    • post_init
    • pre_save
    • pre_delete
    • post_delete
    • post_syncdb

58. Signals: utilizzo di esempio

  • possibile estendere il modellodjango.contrib.auth.models.Userutilizzando un proprio modello attraverso la variabile di configurazioneAUTH_PROFILE_MODULE AUTH_PROFILE_MODULE = 'members.Profile'
  • L'istanza del modello definito inAUTH_PROFILE_MODULEnon viene creata automaticamente nel momento in cui un istanza diUserviene salvata

59. Signals: utilizzo di esempio

  • Possiamo creare l'istanza del modello nella nostra view che si occupa della registrazione dei modelli
  • E se l'utente viene creato da un amministratore attraverso l'interfaccia di amministrazione?

60. Signals: utilizzo d'esempio # pycon2.members.models from django.db import models from django.contrib.auth.models import User from django.dispatch import dispatcher from django.db.models import signals class Profile(models.Model): user = models.ForeignKey(User) photo = models.ImageField(upload_to='pics', blank=True) bio = models.TextField(blank=True) def create_profile(sender, instance, signal, *args, **kwargs): if kwargs.get('created'): try: instance.get_profile() except Profile.DoesNotExist: profile = Profile(user=instance) profile.save() dispatcher.connect(create_profile, signal=signals.post_save, sender=User) 61.

    • Massimo Scamarcia [email_address]
    • http://skam.webfactional.com/

Grazie! http://creativecommons.org/licenses/by-nd/2.5/it/