====== Django ====== Installare i pacchetti Debian * **python-django** * **libapache2-mod-wsgi** Vedere il tutorial **[[http://docs.djangoproject.com/en/1.2/intro/tutorial01/|Writing your first Django app]]**. ===== Progetto e applicazioni ===== * **Progetto** * **Applicazione**: Contenitore di viste (pagine web) e modelli di dati (tabelle del database). Per creare un **nuovo progetto**: mkdir ~/django_projects cd ~/django_projects django-admin startproject my_project cd ~/django_projects/my_project **Creare un database** vuoto e configurare il file **''settings.py''** con le credenziali di accesso. Per popolare il database con le tabelle di servizio lanciare il comando: python manage.py syncdb Le tabelle create dipendono anche dalle **''INSTALLED_APPS''** definite in ''settings.py''. Alcune apps fanno delle domande durante l'installazione iniziale, ad esempio installando ''django.contrib.auth'' verrà chiesto se attivare un account di amministratore. Per **creare un'applicazione** all'interndo del progetto: python manage.py startapp my_app Il progetto grossomodo corrisponde al sito web, dentro il progetto ci sono una o più applicazioni, cioè //unità funzionali// (moduli Python, in effetti) che possono eventualmente essere riutilizzate tra un progetto e l'altro. Ad esempio l'applicazione ''django.contrib.auth'' fornita con Django stesso può essere inclusa per fornire il framework necessario all'autenticazione degli utenti. Per aggiungere l'applicazione al progetto si modifica l'array ''INSTALLED_APPS'' in ''settings.py'': INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'my_app' ) Nella **scelta dei nomi** per il progetto e le applicazioni ricordarsi che: * Il progetto non può avere lo stesso nome di un modulo built-in Python oppure di un modulo Django. Quindi - ad esempio - non sono validi nomi come **''django''** o **''test''**. * Una applicazione non può avere lo stesso nome del progetto. ===== I modelli (le tabelle con i dati) ===== Nell'applicazione si **crea un modello**, cioè si descrive i dati che l'applicazione gestisce. In pratica il modello diventa una **tabella nel database**. Si edita il file **''my_app/models.py''** mettendo qualcosa del genere: from django.db import models from django.contrib.auth.models import User class Expense(models.Model): user = models.ForeignKey(User, verbose_name = 'persona') description = models.CharField('descrizione', max_length=200) date_time = models.DateField('data') amount = models.FloatField('importo') def __unicode__(self): return u'%s, %s' %(self.date_time, self.description) class Meta: ordering = ['-date_time', 'description'] Nell'esempio sopra il modello Expense sarà memorizzato nella tabella **''my_app_expense''**, il campo **''user''** sarà legato con un rapporto di FOREIGN KEY con la chiave primaria della tabella ''auth_user''. Notare che la tabella ''auth_user'' deriva da un'altra applicazione (''django.contrib.auth''), quindi è necessario fare l'import opportuno prima di poterla usare. Il metodo **''%%__unicode__()%%''** del modello verrà usato in tutte le occasioni in cui Django deve mostrare un record della tabella, altrimenti il record sarà presentato come un generico //Expense object//. La classe **''Meta''** consente di aggiungere informazioni non essenziali, ad esempio l'ordinamento predefinito dei dati in questa tabella. Il codice SQL necessario a creare le tabelle per i dati dell'applicazione viene generato con il comando: python manage.py sql my_app Si potrebbe copiare le istruzioni SQL al prompt del database, oppure chiedere a Django di eseguire tutte le istruzioni necessarie (Django verifica quali oggetti esistono già nel database) con il comando: python manage.py syncdb **NOTA**: il comando syncdb si accorge se una tabella esiste già oppure deve essere creata, ma ad esempio non si accorge se una colonna deve essere aggiunta oppure è stata modificata. ===== L'applicazione admin ===== Vedere **[[http://docs.djangoproject.com/en/1.2/ref/contrib/admin/|The Django admin site]]**. Le oprazioni di base sui dati (i //modelli//, cioè le tabelle del database), vale a dire INSERT, DELETE, UPDATE, ecc. possono essere fatti con l'applicazione fornita con **''django.contrib.admin''**. È sufficiente attivarla nel nostro progetto. Nel file **''settings.py''** del progetto aggiungere all'array **''INSTALLED_APPS''** l'applicazione **''django.contrib.admin''**. Ovviamente si deve eseguire il ''python manage.py syncdb'' per sincronizzare l'applicazione con il database. Se vogliamo che i dati della nostra applicazione siano gestibili con l'interfaccia admin bisogna creare un file **''my_app/admin.py''** con le seguenti istruzioni: from my_app.models import Expense from django.contrib import admin admin.site.register(Expense) Infine si deve "attivare" l'URL dell'interfaccia admin, editando il file **''urls.py''** del progetto, in particolare si devono scommentare tre righe nell'esempio creato automaticamente: from django.conf.urls.defaults import * # Uncomment the next two lines to enable the admin: from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', # Example: # (r'^expenses/', include('expenses.foo.urls')), # Uncomment the admin/doc line below to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: (r'^admin/', include(admin.site.urls)), ) L'interfaccia per l'editing della tabella è fortemente personalizzabile definendo un oggetto di tipo **''admin.ModelAdmin''**, tale oggetto va definito nel file **''my_app/admin.py''** ed utilizzato nella funzione ''admin.site.register()'': from my_app.models import Expense from django.contrib import admin class ExpenseAdmin(admin.ModelAdmin): list_display = ('date_time', 'description', 'amount') list_filter = ['date_time'] search_fields = ['description'] ordering = ['-date_time'] admin.site.register(Expense, ExpenseAdmin) ===== Creare una nuova pagina ===== ==== Vista ==== Nel file **''my_app/views.py''** si definisce una funzione Python che restituisce una pagina web, questa sarà una **vista** della nostra applicazione: from django.http import HttpResponse def my_view(request): output = u'''

%s

''' % ( u'Titolo della vista' ) return HttpResponse(output)
==== URL ==== Nel file **''urls.py''** contenuto alla radice del progetto si mappa un nuovo URL sulla vista, cioè sulla funzione Python definita in precedenza nel modulo ''my_project.views''. L'URL viene aggiunto nell'array **''urlpatterns''**, oltre agli altri eventualmente esistenti: from my_app.views import * urlpatterns = patterns('', (r'^my_url/', my_view), ) La pagina sarà visibile all'URL **''/my_url/''** (vedere più avanti come configurare l'URL base del progetto). ==== Mettere i dati nella vista ==== Invece di includere il codice HTML direttamente nella vista (funzione Python) si utilizza il sistema dei **template**. Nella directory radice del progetto si crea la sottodirectory **''templates''** e nel file **''settings.py''** si definisce la **''TEMPLATE_DIRS''** (controllare se il modulo os.path è già incluso): import os.path TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates), ) Quindi si crea il file **''templates/my_view.html''** che conterrà dei segnaposto del tipo **''%%{{nome}}%%''** che verranno sostituiti a runtime:

{{page_title}}

{{page_body}}

Infine si modifica il codice della vista: from django.http import HttpResponse from django.template import Context from django.template.loader import get_template def my_view(request): template = get_template('my_view.html') variables = Context({ 'page_title': u'Titolo della vista', 'page_body': u'Contenuto della pagina' }) output = template.render(variables) return HttpResponse(output) ==== Visualizzazione e aggregazione dei dati ==== Nel codice della **view** è possibile accedere ai dati (i **model**) sfruttando anche le relazioni tra essi (le //foreign key// delle tabelle del database). È possibile anche usare funzioni di aggregazione. Ecco un esempio di una vista dell'applicazione **spese** (**''my_project/spese/views.py''**), che accede ai dati del modello **''User''** e del modello correlato **''Expense''** (sono tabelle correlate). Nella vista si aggiunge un attributo al modello ''User'', aggregando per ogni ''User'' il campo ''amount'' del modello ''Expense'', calcolando la **''Sum''** e il **''Count''**: from django.http import HttpResponse from django.template import Context from django.template.loader import get_template from django.db.models import Sum, Count from django.contrib.auth.models import User #from spese.models import Expense def balance(request): users = User.objects.all() max_expense = 0 for user in users: user.aggr = user.expense_set.aggregate(Count('amount'), Sum('amount')) if user.aggr['amount__sum'] > max_expense: max_expense = user.aggr['amount__sum'] for user in users: user.balance = user.aggr['amount__sum'] - max_expense template = get_template('balance.html') variables = Context({ 'page_title': u'Bilancio spese', 'users': users, 'max_expense': max_expense }) output = template.render(variables) return HttpResponse(output) Ecco infine il template **''my_project/templates/balance.html''** che provvede ad impaginare i dati:

{{page_title}}

{% if users %} {% for user in users %} {% endfor %}
Persona Numero spese Somma spesa Bilancio
{{user.username}} {{user.aggr.amount__count}} {{user.aggr.amount__sum}} {{user.balance}}
{% else %}

No users found.

{% endif %}
===== Webserver di test ===== Per **lanciare il webserver** di test (da non usare in produzione): python manage.py runserver 127.0.0.1:8000 L'opzione predefinita 127.0.0.1:8000 può essere omessa, è possibile indicare altri indirizzi IP, oppure 0.0.0.0 per fare il bind su tutti gli indirizzi. Una cosa molto utile del webserver integrato è che si accorge **se uno dei file sorgenti viene modificato**, in tal caso ricarica il progetto senza bisogno di essere riavviato. ===== Webserver Apache ===== Per eseguire progetti Django con Apache si consiglia di usare il modulo //wsgi//, vedere il tutorial **[[http://docs.djangoproject.com/en/1.2/howto/deployment/modwsgi/|How to use Django with Apache and mod_wsgi]]**. La configurazione che vogliamo realizzare è la seguente: - La configurazione riguarda ad un solo ''VirtualHost''. - Uno o più progetti Django sono accessibili da una sottodirectory della ''DocumentRoot''. - I file di progetto sono salvati fuori dalla ''DocumentRoot''. Fare attenzione soprattutto al punto #3: i file sorgenti del progetto Python **non devono essere pubblicati nella ''DocumentRoot''**. All'interno della ''DocumentRoot'' si crea una sottodirectory - ad esempio di nome **''webapps''** - e nella configurazione del VirtualHost si dichiara quale script deve essere servito come WSGI: ... # Django applications. WSGIScriptAlias /webapps/my_project /var/www/VirtualHost/webapps/my_project.wsgi #WSGIScriptAlias /webapps/ /var/www/VirtualHost/webapps/ Nell'esempio si inserisce una riga WSGIScriptAlias per ogni progetto. In alternativa (riga commentata) si può indicare che tutti gli script presenti nella directory devono essere serviti come script WSGI, in questa seconda ipotesi conviene nominare gli script senza estensione .wsgi. Abbiamo preferito la prima ipotesi perché in tal modo è possibile creare nella directory ''webapps'' un ''index.html'' per elencare le applicazioni esistenti. Inoltre è possibile creare nella stessa directory un link simbolico che punta ai file (CSS, JavaScript, immagini) usati dalle applicazioni. In pratica il contenuto del ''VirtualHost'' non viene modificato, ad eccezione della nuova directory ''webapps''. Nella directory ''webapps'' si crea il file WSGI per ogni progetto Django, ad esempio **''my_project.wsgi''**: import os import sys path = '/usr/local/lib/django' if path not in sys.path: sys.path.append(path) path = '/usr/local/lib/django/my_project' if path not in sys.path: sys.path.append(path) os.environ['DJANGO_SETTINGS_MODULE'] = 'my_project.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() Il file con il progetto Django devono risiedere in una directory fuori dalla ''DocumentRoot'', nell'esempio ''/usr/local/lib/django/''. L'unica modifica che deve essere fatta al progetto (in **''settings.py''**) riguarda la variabile **''ADMIN_MEDIA_PREFIX''**, si imposta: ADMIN_MEDIA_PREFIX = '/webapps/admin_media/' Nella directory webapps si crea il link simbolico: admin_media -> /usr/share/pyshared/django/contrib/admin/media Fortunatamente la variabile ''ADMIN_MEDIA_PREFIX'' viene ignorata quando si utilizza il webserver di test. ==== Compilazione del codice Python ==== L'esecuzione del codice Python viene velocizzato (soprattutto l'avvio) compilando i file **''.py''** nei rispettivi **''.pyc''**. Ciò avviene automaticamente se Python ha i permessi per scrivere nella directory, ma se il codice Django viene eseguito via WSGI l'utente è ''www-data'' che di solito non ha il permesso di scrivere nella directory del progetto. Dare i permessi all'utente ''www-data'' di scrivere nella directory che contiene i sorgenti del progetto non è una buona idea. C'è chi sostiene che il guadagno di performance nell'avere i file compilati è minimo, in quanto il processo WSGI rimane caricato in memoria a lungo e l'overload c'è solo nella fase iniziale di caricamento. Ad ogni modo, lo sviluppatore che volesse compilare tutti i moduli dopo aver apportato delle modifiche, esegue nella directory radice del progetto: python -m compileall . ==== Permessi ==== Fare attenzione al file **''settings.py''** ed al suo compilato **''settings.pyc''** relativi al progetto: contengono le **credenziali di accesso al database**. È necessario che siano leggibili al server web, ma è opportuno che non siano leggibili al mondo. ===== Tips and tricks ===== ==== Formattazione delle date ==== Nelle impostazioni del progetto (**''settings.py''**) vengono impostati diversi parametri per la localizzazione dell'applicazione: TIME_ZONE = 'America/Chicago' LANGUAGE_CODE = 'en-us' USE_L10N = True In particolare la variabile **''USE_L10N''** impostata a **''True''** significa che verranno usate automaticamente le convenzioni locali per formattare date, numeri, ecc. Per forzare ad esempio il formato data in qualcosa di diverso si può impostare: USE_L10N = False DATE_FORMAT = '%Y/%m/%d'