An Introduction to Using CouchDB with Django
Un exemple d’utilisation de CouchDB avec django, le framework python.
Cet article est le dernier d’une série de 5 visant à expliquer le couplage lâche de django et son intérêt.
Tester ces templatetags django
Après python [fr], je suis en train de découvrir django [en].
J’ai découvert django il y a bientôt 3 ans, au moment de choisir un outil pour refondre notre LMS. À l’époque, j’avais été déjà séduit par django, bien que je l’ai finalement écarté car il fallait apprendre un nouveau langage, ainsi que parce qu’il me semblait que trouver des compétences python/django serait plus compliqué que pour PHP et symfony, ce qui finalement, n’est d’ailleurs pas si vrai.
Bref je travaille actuellement sur un petit projet dont j’espère pouvoir bientôt vous parler. J’apprécie en particulier la facilité avec laquelle on peut tester un partie de son code, mais parfois, arriver à lancer le script requiert un peu de doigté. En particulier, j’ai eu un peu de mal à trouver comment tester mes templatetags. Après quelques heures de bataille, voici comment effectuer des doctests:
// my_project/my_app/templatetags/my_tags.py
if __name__ == "__main__":
import os.path, sys, doctest
from django.core.management import setup_environ
PROJECT_PATH = os.path.dirname(os.path.abspath('../../my_project'))
sys.path.append(PROJECT_PATH)
try:
import settings
except ImportError:
print "Unable to import settings."
sys.exit(1)
setup_environ(settings)
from django.conf.urls.defaults import *
from my_app.urls import urlpatterns
doctest.testmod()
django_hg
Je viens de mettre à disposition django_hg [en] sur bitbucket [en].
L’objectif de django_hg est de permettre la visualisation dans un projet django [en] d’un repository Mercurial [en].
Si vous connaissez un peu Mercurial, vous savez qu’un serveur web est intégré par défaut. Pourquoi donc refaire ce qui existe déjà ?
- Pour apprendre Python !
- Pour apprendre django !!
- Pour apprendre Mercurial !!! Sur ces points, je trouve que ça se passe plutôt bien, dans l’ensemble, pas trop de galère, à part la mise en place des doctests sur les templatetags.
- et aussi parce que je pense qu’il est plus facile d’adapter des templates django que ceux de Mercurial,
- parce qu’à terme je souhaite ajouter des fonctions telles que l’intégration de l’authentification django. Cela va d’ailleurs être mon prochain travail, en me basant sur django-projectmgr [en].
Progression de django_hg
La semaine passée, j’ai refactorisé l’application de façon à ce que la création des repositories et la gestion des droits se fassent par django [en]. En fait, je me suis largement basé sur django-projectmgr [en] pour le modèle, en modifiant de façon marginale ça et là.
Actuellement la lecture se fait donc selon les droits d’accès et la création d’un nouveau repository est disponible depuis l’admin.
Dans l’ensemble, le refactoring a été simple. La seule difficulté que j’ai rencontrée se situe au niveau de la requête affichant la liste, car je voulais récupérer tous les repositories visibles par un utilisateur en une requête, contrairement à django-projectmgr. Or l’ORM de django ne m’a pas facilité la tâche. J’ai dû utiliser la méthode extra [en], pour un résultat pas très lisible. Pour faire une comparaison avec le monde PHP, je dirais que c’est mieux que Propel [en], mais moins bien que Doctrine [en].
Cette semaine, je compte m’attaquer à permettre le clonage depuis django_hg. J’ai l’impression que cela va être nettement moins simple… car jusqu’à présent, je n’ai pas trouvé grand chose. Le serveur intégré permet le clonage, mais je n’arrive pas trop à savoir où cela se passe. J’ai trouvé un fichier streamclone.py qui semble-t-il permet l’envoi du contenu d’un repository en streaming, mais pour l’instant, je ne suis pas parvenu à en tirer grand chose. La documentation de Mercurial sur HTTP est assez succinte [en].
Si vous voulez récupérer le code, c’est ici [en].
django_hg sait cloner
J’ai profité du pont pour travailler sur l’un des points qui me semblait le plus critique dans django_hg, la gestion par django des requêtes HTTP utilisées pour cloner (et pour puller.)
Je dois dire que cela s’est très bien passé, bien plus que je ne l’aurai pensé, grâce notamment à l’aide de la mailing-list (et en particulier de Dirkjan Ochtman [en] et Benoît Boussicot [en].) En fait, ce qui m’a le plus retenu, c’est que je m’attendais tellement à galérer que j’ai fait des erreurs bêtes et tout développeur sait bien que ce sont les erreurs les plus évidentes qui sont les plus difficiles à corriger !
Faire traiter par django les requêtes d’un clone est donc très simple:
# views.py
def commands(request, name):
""" handles the commands sended by an hg client (clone, pull, push)
``name``
the name of the repository
"""
from mercurial import hg, ui
from mercurial.hgweb.request import wsgirequest
from mercurial.hgweb import protocol
cmds = ['between', 'branches', 'capabilities', 'changegroupsubset', 'heads']
r = hg.repository(ui.ui(), name)
req = wsgirequest(request.META, None)
if request.GET.get('cmd') in cmds:
resp = protocol.__getattribute__(request.GET.get('cmd'))(r, req)
return HttpResponse(resp, protocol.HGTYPE)
Les imports correspondent aux différents objets de Mercurial. On note en particulier que les requêtes sont des objets wsgirequest. Cela tombe bien, django aussi, à ceci prêt que la request django engloble les données nécessaires à la création d’une requête wsgi dans le dictionnaire request.META.)
La liste des commandes disponibles est décrite dans cmds. Il suffit de créer en plus un objet repository r et après avoir vérifié que la commande est bien une des commandes attendues par l’objet protocol qui gère toutes les étapes [en] d’un clone, on obtient un flux qu’il suffit de retourner à Mercurial avec le bon type MIME, avec l’objet HttpResponse de django, dont l’inclusion se fait au niveau du module.
Simplissime, non ? Vous pouvez voir le code en situation sur bitbucket [en].
Ce code ne gère pas l’authentification. Je compte me pencher sur la question cette semaine, même si auparavant, je voudrais me pencher un peu sur ce blog qui est un peu en vrac depuis que Free, sur lequel j’héberge encore les images, est en carafe…
Creating a development https server on django
En train de travailler à l’authentification de django_hg, je suis tombé sur ce post bien utile !
Authentification avec django_hg
La semaine dernière [fr], j’ai réussi à utiliser django [en] pour répondre aux commandes de Mercurial.
L’étape suivante dans cette logique était donc de faire l’authentification grâce à django. Le code a été poussé sur bitbucket [en] hier soir. Je n’ai par contre pas eu le temps d’écrire la documentation, j’espère pouvoir faire cela dans la journée.
Si l’authentification est finalement contenue dans un decorator, j’ai fait pas mal d’administration pour mettre en place un environnement de développement. En effet, j’ai tout d’abord configuré Apache pour qu’il serve django. Cela n’a pas été sans mal, car la configuration de mod_wsgi m’a donné un peu de fil à retordre.
La documentation officielle de django [en] recommande d’utiliser mod_wsgi [en]. Je me suis donc tourné vers cette solution. Malheureusement, la version distribuée par MacPort [en] est vieille (version 1.1 alors que nous en sommes à la 2.5 en stable et que la 3.0 est en RC) et surtout elle est couplée à Python2.4, alors que django utilise Python2.5. J’ai donc dû en passer par la compilation.
La documentation de mod_wsgi est très bien faite et elle explique bien les problèmes que l’on peut rencontrer avec MacOSX [en], essentiellement dû au fait que MacOSX supporte 4 architectures (PPC7400 — 32 bits —, PPC64 — 64 bits —, i386 — 32 bits— et x86_64 — 64 bits) mais que tous les programmes ne sont pas forcément distribués avec les 4 codes. En l’occurence, Apache et Python ont été compilé en i386 uniquement… mais voilà, même si on lui indique le chemin de Python a utilisé, le compilateur va chercher à un moment donné celui installé par défaut. En l’occurence, il s’agit d’une installation via MacPython, effectuée sur mon précédent Mac, avec un code PPC7400. Il m’a fallu un peu de temps avant de m’en rendre compte et remplacer le fichier PPC par un fichier i386 et permettre à la compilation de se terminer.
Du coup, entre temps, j’ai installé mod_python pour pouvoir avancer un peu sur la configuration de django via Apache. Pas de remarque particulière, l’installation se fait sans difficulté via MacPort et la configuration ne m’a pas posé de problème particulier, y compris l’authentification HTTP via Apache, django proposant un backend [en] pour cela. Ceci étant, ce n’était qu’une solution très provisoire, car mod_python ne permet pas d’utiliser Mercurial via django. En effet, le principe de django_hg est d’utiliser la requête django pour construire une requête Mercurial qui est en fait une requête wsgi. Ca fonctionne avec le serveur de développement de django car celui-ci est également wsgi. Évidemment, ce n’est pas le cas lorsque django est servi via mod_python.
D’où mon retour sur mod_wsgi. Une fois la compilation réussie, la configuration m’a donné un peu de mal également. En fait, là aussi, la doc est très claire, encore faut-il penser à vérifier le chemin de django. En effet, si django n’est pas dans le path du dossier où est exécuté le script wsgi, il faut ajouter django à l’aide de sys.path.append("/chemin/vers/django/"). Cependant, le piège par rapport aux exemples de la doc, c’est qu’avec MacPort, django est installé dans opt/local/lib/python2.5/site-packages/django/bin/. Or si on inclue juste opt/local/lib/python2.5/site-packages/django/, on peut inclure django, accéder aux settings, mais le script crash après. Mais si l’on lit bien la documentation, il est bien précisé qu’il faut mettre le chemin où se situe django-admin.py. Voilà qui m’apprendra à lire les documentations au pied de la lettre !
Cela fait, le reste a été un jeu d’enfant, mais je réserve le détail à un prochain article.
Authentification avec django_hg, partie 2
La dernière fois, nous avons vu comment servir django avec mod_wsgi.
Une fois Apache correctement configuré, effectuer l’authentification via django a été un jeu d’enfants.
Tout d’abord, rééditer la configuration du virtual host Apache pour ajouter cette ligne :
WSGIPassAuthorization On
On redémarre ensuite Apache.
Cette ligne informe Apache de transmettre les informations d’authentification au programme wsgi, en l’occurence django. Sinon, par défaut, Apache ne les transmets pas [en].
Ensuite, ajouter un decorator devant les vues que vous souhaitez protéger par l’authentification HTTP. Je me suis basé sur ce snippet [en] en l’adaptant au cas particulier de django_hg, c’est à dire que je souhaite une authentification HTTP uniquement si c’est un client Mercurial qui tente une commande clone, push ou pull.
Et voilà, le tour est joué
Pour détecter le cas de figure, je m’étais dans un premier temps basé sur le HTTP_USER_AGENT transmis par le client. Seulement je me suis dit que cette valeur risquait bien de changer, selon les clients ou avec le temps, même si Murky [en] par exemple, utilise le même.
Je suis donc en train de refactoriser cette partie en me basant uniquement sur la présence de l’argument cmd dans l’url et en vérifiant la validité. Pour cela j’ai mis en place un certain de tests unitaires, pour vérifier que je ne créais pas de régression. J’ai commité le résultat hier soir sur bitbucket [en].
Le dernier gros point complexe du programme est en passe d’être achevé. Je souhaite effectuer une dernière modification pour reproduire le comportement classique des projets open-source: clone et pull peuvent être anonymes mais pas le push, pour des raisons évidentes de sécurité et de traçabilité.
Honnêtement, au départ, je ne pensais être en mesure d’intégrer à ce point django et Mercurial, en tout cas, certainement pas aussi facilement.
La suite devrait être assez simple: ajouter les fonctionnalités manquante (diff, branches, etc.) et faire un outil de ticketing.
django_hg : l'authentification au travers de django est OK
Cette semaine a vu une étape importante dans la conception de django_hg : l’authentification — en HTTP(S) uniquement — des commandes clone, push et pull envoyées par Mercurial est totalement gérée par django.
Elle respecte les deux schémas suivants:
- Pour les projets publics (avec
anonymous_accessàTrue),cloneetpullsont anonymes.pushrequiert une authentification, l’utilisateur devant appartenir avoir la permissionread/writepour le projet. C’est un schéma que l’on trouve typiquement pour les projet open-source. - Pour les projets privés (
anonymous_accessàFalse), toutes les commandes requièrent d’avoir la permissionread/writepour le projet.
Dans ce contexte, la permission read ne donne en fait accès qu’à l’affichage dans le navigateur. Je pense donc la renommer en web view. Ce changement de nom n’aura aucun impact sur le programme, mais cela clarifiera les rôles.
Je vais désormais travailler sur l’interface du projet et il faut que je vois un peu comment sont gérés CSS et images dans les applications réutilisables django, telles que celles que l’on trouve dans pinax [en]. Il faut aussi que je complète les fonctionnalités manquantes (diff, branches, …)
Par la suite, je pense ajouter un mini gestionnaire de bugs, pour en faire un outil simple mais complet.
django_hg : authentication throught django is OK
This is an important step in django_hg build has been reached : authentication — HTTP(S) only — of clone, pushand pullcommands sended by Mercurial is completely handled by django.
There’s two majors workflows:
- For public projects (with
anonymous_accesssets toTrue),cloneetpullare anonymous.pushrequires an authentication, the user must have theread/writepermission for the projet. This schema is often used for open-source projects. - For private projects (
anonymous_accesssets toFalse), all commands require to haveread/writepermission for the projet.
In this context, read permission only allow to display project in the browser. I will rename it into web view. This change won’t have any impact on the software, but it will clarify things.
In the next weeks, I will work on the UI, which leads me to see how CSS and images are handled in django reusable apps, like those found in pinax [en]. I also need to complete unavailable functionalities, like diff, branches, …
Afterwards, I think I will add a mini bugtracker, so django_hg will be a simple yet complete tool.