Deployment di un progetto Django

Siccome Django è un framework web e siccome esso mette a disposizione degli strumenti per il test locale, in un corretto modello di sviluppo e testing la parte in cui i cambiamenti vengono resi noti al mondo esterno in un server reale, rappresentano un passaggio importante e delicato; i passaggi devono essere automatizzati in maniera tale da evitare di dimenticarli o compierli in maniera errata (sopratutto se il sito in questione non è usato direttamente dallo sviluppatore, il quale non può accorgersi di eventuali problemi anche piccoli che possono presentarsi).

Possiamo dividere le buone pratiche per un corretto deploy (TM) principalmente in due categorie:

Uno dei problemi più sentiti, che potenzialmente può essere molto lungo da risolvere e, sopratutto, non prevedibili consiste nella gestione delle dipendenze a cui è soggetta la tua web application, sopratutto nel caso in cui essa conviva nello stesso shared hosting con altre che richiedono versioni diverse delle librerie: per risolvere questo scenario sono possibili due vie (nel caso in cui voi usiate un version manager software, ma voi lo usate vero;-))

Io propendo per la seconda opzione siccome permette di lasciare più snello il progetto, evitando di portarsi dietro MB di librerie (spesso in forma binaria) che poi risulterebbe veramente difficile poter aggiornare se non sporcando inevitabilmente la storia del vostro repository (nel caso in cui voi usiate un version manager software, ma voi lo usate vero;-)). Ovviamente per lo stesso motivo sarebbe inutile tenere sotto versioning la sandbox anche nell’ottica di una automatizzazione: mica volete uno script che vi registri anche l’hosting in automatico? certi passaggi vanno fatti bene e automatizzati ma, è questo il punto importante, a parte e non assieme al deploy vero e proprio, magari con tempistiche proprie e coerenti con il loro scopo.

Impostazioni iniziali

Sempre nell’ottica della customizzabilità delle impostazioni della vostra web application, è pratica giusta e buona inserire nel file settings.py le impostazioni comuni con i settaggi più restrittivi ed usare un file local_settings.py da importare con le specifiche impostazioni per la specifica installazione. Infatti una delle cose più fastidiose all’inizio è dover cambiare tutte le variabili che impostano i percorsi, in alcuni casi assoluti, che sicuramente cambieranno già solo nei momenti di debug in computer usati per lo sviluppo; un esempio su tutti è la variabile TEMPLATE_DIRS.

Quindi primo passo è creare una variabile che contenga il path del progetto nel nostro filesystem usando la keyword __file__

 PROJECT_ROOT_PATH = os.path.abspath(os.path.dirname(__file__))

modifichiamo MEDIA_ROOT, MEDIA_URL e ADMIN_MEDIA_PREFIX

 MEDIA_ROOT = PROJECT_ROOT_PATH + '/media/'
 MEDIA_URL = '/media/'
 ADMIN_MEDIA_PREFIX = '/admin_media/'

Cancelliamo dal file globale la SECRET_KEY e creiamone una nuova nelle impostazioni locali per vari motivi:

Rendere ROOT_URLCONF locale, cioé togliendogli il prefisso con il nome del progetto

 ROOT_URLCONF = 'urls'

Indicare la directory dei templates globale

 TEMPLATE_DIRS = (
     PROJECT_ROOT_PATH + 'templates/'
 )

Infine al termine del file inserire questa porzione di codice che legge da un file locale le impostazioni, in maniera tale da avere per ogni sito la propria configurazione ottimale

 # local_settings.py can be used to override environment-specific settings
 # like database and email that differ between development and production.
 try:
     from local_settings import *
 except ImportError:
     print 'Do you have a \'local_settings\'?'
 
 TEMPLATE_DEBUG = DEBUG
 MANAGERS = ADMINS

Le ultime due righe permettono di impostare quei valori in base al contenuto di local_settings.py.

Static Files

Uno dei piccoli problemi che si incontra nel deploy è quello dei file statici che in un server di produzione non devono essere serviti da django; per la parte propria di django il seguente pezzo di codice rende i file serviti dal nostro amato framework sono nel caso in cui si stia eseguendo il debug

 # urls.py
 if settings.DEBUG:
	urlpatterns += patterns('',
		(r'^media/(?P<path>.*)$', 'django.views.static.serve',
			{'document_root': settings.MEDIA_ROOT,
				'show_indexes': True}),
	)

Può essere utile anche un file .htaccess nel caso utilizziate un server Apache con mod_rewrite abilitato

 RewriteEngine On
 RewriteBase /
 RewriteRule ^(media/.*)$ - [L]
 RewriteRule ^(admin_media/.*)$ - [L]
 RewriteRule ^(dispatch\.fcgi/.*)$ - [L]
 RewriteRule ^(.*)$ dispatch.fcgi/$1 [L]

Per admin_media è presumibile fare un link simbolico (che si spera il web server permetta) alla directory django/contrib/admin/media/.

Mentre si scrive il codice

Personalmente uso un preciso schema per tenere a mente e verificare quali passaggi mancano nel codice

Dipendenze

Utilizzando pip è possibile ottenere un modo automatizzato di verificare/installare le dovute dipendenze tramite un semplice comando ed in più ottenere tramite un semplice file di testo l’elenco delle stesse.

Però prima di pensare in un progetto specifico alle dipendenze ritengo più utile sviluppare le funzionalità di base ottenendo così di non perdere tempo inutilmente a cambiare di continuo il file delle dipendenze.

Quando ci si ritrova ad un punto “di arrivo” (magari nel deploy su un server esterno) è possibile usare pip per ottenere un elenco delle dipendenze che attualmente sono presenti localmente.

$ pip freeze

Spesso ci si ritrova a dover lavorare su diversi progetti che devono condividere la stessa macchina pur non condividendo le stesse librerie (oppure condividendole ma non le loro versioni utili). Si necessita quindi di avere degli ambienti puliti e isolati per ogni progetto: a questo serve virtualenv.

 $ cd path/to/project
 $ virtualenv --no-site-packages env
 New python executable in env/bin/python2.6
 Also creating executable in env/bin/python
 Installing setuptools............done.
 $ source env/bin/activate
 (env) $

Come si può notare il prompt è cambiato e questo indica che si sta usando esclusivamente il python contenuto dentro la directory env/.

Se si usa debian come distribuzione è possibile che si abbia una versione di pip un po’ obsoleta (tipo 0.3 contro una versione 0.6 disponibile sul sito) quindi è possibile usare il virtualenv appena impostato e scaricare la nuova versione

(env) $ easy_install pip

e di seguito installare da un file di dipendenze creato precedentemente

(env) $ pip install -r dependencies.txt # --ignore-installed

N.B: attenzione che la variabile PYTHONPATH non punti da qualche parte esterna al virtualenv in quanto lo scavalcherebbe.

Ancora prima di eseguire un test live tramite l’utilizzo di un browser, nel caso si stia utilizzando WSGI/Fcgi come interfaccia per eseguire Django sul server potete provare a lanciare da linea di comando l’eseguibile stesso

$ PATH_INFO=/path/to/page ./dispatch.fcgi

ed osservare se effettivamente risultano o no errori (/path/to/page è il percorso della pagina che volete testare il quale, per le specifiche di queste gateway interfaces, deve essere passato attraverso variabili d’ambiente, cioé PATH_INFO in questo caso).

Dati iniziali

Una volta caricato il codice sul server è necessario prima di tutto controllare che il codice funzioni, lanciando gli unit test (perché voi scrivete gli appositi tests per ogni vostra unità si codice, vero?); in caso contrario significa che i passaggi effettuati precedentemente sono stati fallaci.

Confermato che il codice funzioni sul vostro server, escludendo problemi di dipendenze (o al massimo trovandone di nuove :-P) possiamo passare al rendere operativo il sito; come da manuale, lanciare un syncdb dovrebbe bastare per risolvere tutti i vostri problemi se avete avuto l’accortezza di inserire all’interno di una directory fixtures una fixture appunto, denominata initial_data relativa a dati fissi del vostro sito (come per esempio delle flatpages)

$ python manage.py syncdb

Ma quali sono i dati che in generale sono necessari includere nel progetto?

 $ python manage.py shell
 >>> from django.contrib.auth.models import User
 >>> help(User.objects.create_user)
 Help on method create_user in module django.contrib.auth.models:

 create_user(self, username, email, password=None) method of django.contrib.auth.models.UserManager instance
    Creates and saves a User with the given username, e-mail and password.
 >>> User.objects.create_user('gp', 'gp@no.spam.org', password='password')

Backup

Non vi crederete mica che una volta messo su il sito voi abbiate finito? esiste il problema dell’eventuale backup che è sempre meglio fare, perché per quanto siate intelligenti e fortunati l’imprevedibile è sempre in agguato e perdere magari anni di lavoro non è mai bello. Tralasciamo quale sia la migliore via per fare un backup, limitandoci a pensare al come e non al dove (non che il dove non sia già impegnativo); come sempre ci sono due vie

La seconda via è molto più comoda nel caso in cui ci si ritrovi a dover ricaricare i dati in un progetto simile, tuttavia se non si ha la possibilità di utilizzare django-admin si ha un cumulo di dati non direttamente importabili. Al contrario è possibile ricaricare in qualunque caso i dati dumpati con il database nel suo formato nativo.