ka.da

Aller au contenu | Aller au menu | Aller à la recherche

Mot-clé - programmation

Fil des billets - Fil des commentaires

vendredi, avril 1 2011

Django : passage aux vues génériques basées sur des classes, par l'exemple

Introduction

Si vous utilisez Django vous connaissez probablement déjà les vues génériques/generic views : elles permettent de faciliter la création de certaines tâches monotones et répétitives qu'on retrouve souvent sur les applications web comme sont les listes, affichage, modification et création d'objets...

Jusqu'à Django 1.2 les vues génériques étaient des fonctions, et depuis Django 1.3 (qui est sorti il y a peu) elles sont maintenant basées sur des classes. Je vais tenter ici, modestement, de montrer l'intérêt de ce changement, donner des exemples simples pour illustrer la migration d'un type vers l'autre, et donner quelques repères pour comprendre leur fonctionnement.

Les vues génériques basées sur des fonctions (qu'on notera FBV) fonctionnent très bien pour des cas simples, puis on doit rapidement faire de l'encapsulation ('wrapper') pour certains cas spéciaux, mais on arrive encore à notre but, mais à un moment ou à un autre, malheureusement on se retrouve coincé sans pouvoir faire ce qu'on veut et on doit alors recréer nos propres vues et se passer de ce qui existe déjà dans Django.

Cette limite est connue depuis longtemps et le travail a été long (le ticket #6735 a trois ans) mais l'arrivée des CBV permet justement de s'affranchir de ses limites.

Exemple de passage des FBV aux CBV

Prenons un cas simple où l'on veut lister des objets, et où la liste de ceux-ci dépendra d'un paramètre dynamique (ici l'utilisateur connecté) :

note : j'ai volontairement allégé le code en enlevant les parties classiques ou évidentes, néanmoins si vous voyez une énormité merci de le signaler

## urls.py
from myproject.views import listview

urlpatterns = patterns('',
    (r'^list/', listview, {}, 'myobject_list'),
)

## models.py
from django.contrib.auth.models import User

class MyObject(models.Model):
    ...
    author = models.ForeignKey(User)

## views.py
@login_required
def listview(request,
          queryset=MyObject.objects.all(),
          template_name='myproject/myobject_list.html'):
    qs = queryset.filter(author=request.user)
    return object_list(request,
                       queryset=qs,
                      template_name=template_name)

On a ici l'exemple typique de l'utilisation d'une vue générique avec un wrapper. Jusque là ça ne pose pas de problème particulier et tout peut être fait avec Django 1.2 et ses FBV.

Par contre, vu que dans notre modèle author est un utilisateur, on imagine très bien qu'il faut qu'à l'enregistrement ce champ soit initialisé avec l'utilisateur connecté, donc la valeur de request.user, et là les vues génériques montrent leur limitation : il est en effet possible de surcharger la fonction save() du formulaire mais il n'est pas possible d'indiquer à la vue générique create_object de lui passer le paramètre qui nous sera utile au moment de son enregistrement.

On est donc obligé de créer notre propre vue pour gérer ce qui est finalement une différence mineure mais un cas que l'on retrouve fréquemment.

On aura alors quelque chose comme

## urls.py
urlpatterns = patterns('',
    (r'^list/', listview, {}, 'myobject_list'),
    (r'^create/', createview, {}, 'myobject_create'),
)

## forms.py
class MyObjectForm(ModelForm):
    class Meta:
        model = MyObject
        exclude = ('author',)

    def save(self, user=None):
        myobject = super(MyObjectForm, self).save(commit=False)
        myobject.author = user
        myobject.save()

## views.py
from myproject.forms import MyObjectForm

@login_required
def createview(request, form_class=MyObjectForm):
    if request.method == 'POST':
        form = form_class(request.POST)
        if form.is_valid():
            myobject = form.save(user=request.user)
            return HttpResponseRedirect(myobject.get_absolute_url())
    else:
        form = form_class()
    return render_to_response('myproject/myobject_form.html',
                              {'form': form},
                              context_instance=RequestContext(request))

Voyons maintenant comment les deux exemples précédents sont implémentables en utilisant les vues basées sur les classes.

## views.py

class ListView(generic.ListView):
    queryset = MyObject.objects.all()
    template_name = "myproject/myobjects_list.html"

    def get_queryset(self):
        return self.queryset.filter(author=self.request.user)
listview = login_required(ListView.as_view())

pour la liste d'objets, ok le code est un peu plus court, mais ça ne change pas grand-chose finalement. Voyons maintenant la vue implémentant la création d'objet avec l'enregistrement de l'utilisateur connecté dans le champ author

## views.py

from myproject.forms import MyObjectForm

class CreateView(generic.CreateView):
    form_class = MyObjectForm
    template_name = "myproject/myobject_form.html"

    def form_valid(self, form):
        self.object = form.save(user=self.request.user)
        return super(CreateView, self).form_valid(form)
createview = login_required(CreateView.as_view())   

Ici on arrive à réutiliser toute le code fourni par Django et ne redéfinir que quelques valeurs (form_class, template_name) et juste la fonction form_valid() c'est-à-dire celle qui définit ce qui se passe quand le formulaire est valide et qu'on veut le sauvegarder.

Utilisation

Comme je l'ai dit en introduction, les class-based views ont été implémentées dans Django 1.3 mais vous avez de la chance puisque que le code a été backporté dans Django 1.2 et vous pouvez donc d'ores et déjà les essayer/utiliser même si vous n'avez pas encore migré en 1.3.

Donc si vous êtes en 1.3 vous devez ajouter l'import suivant dans vos views.py

from django.views import generic

Si vous êtes encore en 1.2, il vous faut installer l'application django-cbv

$ pip install django-cbv

Ajouter le middleware qui va bien dans settings.py

MIDDLEWARE_CLASSES = (
    ...
    'cbv.middleware.DeferredRenderingMiddleware',
)

et, dans vos views.py utiliser l'import suivant :

import cbv as generic

Cette technique, directement tirée de l'article de Bruno Renié (que je remercie puisque c'est cet article et la découverte de cette possibilité qui m'a fait enfin m'intéresser sérieusement aux CBV, tout en restant en Django 1.2), vous permet ensuite de aisément migrer en 1.3 en n'ayant besoin de changer que l'import.

Aller plus loin

Bon, ça c'était un exemple simple mais bien évidemment les CBV sont à la fois plus complexes et plus puissantes. Je vous encourage fortement à aller lire la doc (présentation et référence) ainsi que le code, mais en gros voici comment ça se passe (de ce que j'en ai compris pour l'instant, n'hésitez pas à me corriger si je dis une connerie) :

  • chaque FBV qui existait auparavant existe maintenant sous la forme d'une CBV et vous pouvez retrouver simplement le même comportement
  • chaque vue générique est formée de un ou plusieurs "mixin"
  • chaque mixin implémente une fonctionnalité comme générer un template, afficher une liste d'objets, créer et afficher des formulaires
  • les vues génériques utilisent les capacités d'héritage des classes pour, en mélangeant (mixant ;-) un ou plusieurs mixins, produire le comportement désiré
  • pour redéfinir le comportement d'une vue, il suffit de redéfinir un ou plusieurs paramètres, voire une ou plusieurs fonctions provenant des mixins, comme je l'ai fait dans l'exemple ci-dessus
  • si vous avez besoin d'utiliser plusieurs fois le même comportement, il vous suffit de définir vos propres mixins et de créer vos propres vues en héritant de ceux-ci et de ceux prédéfinis.

Conclusion

Les exemples donnés ici sont simples, et l'article n'a pas pour vocation à vous transformer en 'powerusers' des CBV mais j'espère qu'il vous permettra de vous donner une première vision de leur fonctionnement et vous encouragera à lire la documentation et le code associé pour découvrir leur puissance.

bonus : si quelqu'un sait (ou trouve) comment utiliser les CBV pour gérer une vue avec plusieurs formulaires en même temps je suis preneur de l'information...

dimanche, juin 6 2010

Rapide guide pratique pour l'utilisation des branches Mercurial


L'article de Steve Losh A Guide to Branching in Mercurial permet de se faire une bonne idée du fonctionnement et des possibilités offertes par Mercurial pour gérer des "branches" (afin d'avoir des lignes de développement distinctes).

L'article Mercurial Workflows: Stable & Default quant à lui présente plus précisément la solution "branches nommées" (named branches).

Mais il manque d'exemples pratiques à mon goût, et comme je devais l'expliquer en détail à des amis, voici ma tentative de guide pratique appliqué à la théorie expliquée dans l'article sus-cité.

On commence par initialiser un projet, à créer quelques fichiers et à les committer séparément

$ hg init projet
$ cat > a
hello a
^D
$ hg add a
$ hg commit -m 'ajout a'
$ hg glog --style compact
@  0[tip]   a82264640870   2010-06-05 23:39 +0200   eric
     a

$ cat > b
hello b
^D
$ hg add b
$ hg commit -m 'ajout b'
$ hg glog --style compact
@  1[tip]   39bf03acf954   2010-06-05 23:41 +0200   eric
|    ajout b
|
o  0   a82264640870   2010-06-05 23:39 +0200   eric
     ajout a


On a pour l'instant une seule branche

$ hg branches
default                        1:39bf03acf954


On en crée une sous le nom stable. Elle sera utilisée pour gérer les corrections de bugs de la version stable, tandis que le développement courant aura lieu dans la branche default

$ hg branch stable
marked working directory as branch stable
$ hg commit -m 'stable branch'
$ hg branches
stable                         2:97c9284f569c
default                        1:39bf03acf954 (inactive)


Pour l'instant, l'historique des révisions reste linéaire

$ hg glog --style compact
@  2[tip]   97c9284f569c   2010-06-05 23:42 +0200   eric
|    stable branch
|
o  1   39bf03acf954   2010-06-05 23:41 +0200   eric
|    ajout b
|
o  0   a82264640870   2010-06-05 23:39 +0200   eric
     ajout a


On met à jour le dépôt pour utiliser la branche default et on modifie à nouveau des fichiers

$ hg update default
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg branches
stable                         2:97c9284f569c
default                        1:39bf03acf954 (inactive)
$ cat >> a
bye a
^D
$ cat a
hello a
bye a


Quand on committe notre code, une nouvelle "tête" (head) est créée et l'historique n'est alors plus linéaire, ce qui montre que la branche default diverge de la branche stable, celle-ci étant un sous-ensemble de la première

$ hg commit -m 'bye a'
created new head
$ hg heads --style compact
3[tip]:1   bc970099e4f8   2010-06-05 23:53 +0200   eric
  bye a

2   97c9284f569c   2010-06-05 23:42 +0200   eric
  stable branch
$ hg glog --style compact
@  3[tip]:1   bc970099e4f8   2010-06-05 23:53 +0200   eric
|    bye a
|
| o  2   97c9284f569c   2010-06-05 23:42 +0200   eric
|/     stable branch
|
o  1   39bf03acf954   2010-06-05 23:41 +0200   eric
|    ajout b
|
o  0   a82264640870   2010-06-05 23:39 +0200   eric
     ajout a


Faisons de nouvelles modifications dans la branche de développement principal

$ cat > c
hello c
^D
$ hg add c
$ hg commit -m 'ajout c'
$ hg glog --style compact
@  4[tip]   290b943b61d5   2010-06-05 23:54 +0200   eric
|    ajout c
|
o  3:1   bc970099e4f8   2010-06-05 23:53 +0200   eric
|    bye a
|
| o  2   97c9284f569c   2010-06-05 23:42 +0200   eric
|/     stable branch
|
o  1   39bf03acf954   2010-06-05 23:41 +0200   eric
|    ajout b
|
o  0   a82264640870   2010-06-05 23:39 +0200   eric
     ajout a


Et retournons dans la branche stable pour corriger quelques bugs

$ hg update stable
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ cat >> a
fix a
$ hg commit -m 'fix a'
hg glog --style compact
@  5[tip]:2   26c5f5e23509   2010-06-05 23:55 +0200   eric
|    fix a
|
| o  4   290b943b61d5   2010-06-05 23:54 +0200   eric
| |    ajout c
| |
| o  3:1   bc970099e4f8   2010-06-05 23:53 +0200   eric
| |    bye a
| |
o |  2   97c9284f569c   2010-06-05 23:42 +0200   eric
|/     stable branch
|
o  1   39bf03acf954   2010-06-05 23:41 +0200   eric
|    ajout b
|
o  0   a82264640870   2010-06-05 23:39 +0200   eric
     ajout a


Maintenant il faut bien évidemment que ces corrections de bugs se retrouvent également dans la branche de dev default

$ hg update default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg merge 26c5f5e23509
merging a
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ hg commit -m 'merge with 26c5f5e23509 (stable branch)'
$ hg glog --style compact
@  6[tip]:4,5   fb323a167af1   2010-06-05 23:57 +0200   eric
|\     merge with 26c5f5e23509 (stable branch)
| |
| o  5:2   26c5f5e23509   2010-06-05 23:55 +0200   eric
| |   fix a
| |
o |  4   290b943b61d5   2010-06-05 23:54 +0200   eric
| |    ajout c
| |
o |  3:1   bc970099e4f8   2010-06-05 23:53 +0200   eric
| |    bye a
| |
| o  2   97c9284f569c   2010-06-05 23:42 +0200   eric
|/     stable branch
|
o  1   39bf03acf954   2010-06-05 23:41 +0200   eric
|    ajout b
|
o  0   a82264640870   2010-06-05 23:39 +0200   eric
     ajout a


On voit bien que le fichier a contient le code venant des deux branches (même si ça se joue au moment du merge)

$ cat a
hello a
fix a
bye a


Quand vous voulez remplacer la branche stable il suffit de merger avec default

$ hg update stable
$ hg merge 290b943b61d5
$ hg commit -m 'merge with 290b943b61d5'


Pour fermer définitivement la branche, utiliser l'option --close-branch à hg commit.

et voili...

note : l'article a été écrit rapidement donc n'hésitez pas à signaler toute erreur

dimanche, février 14 2010

Django : gérer plusieurs domaines avec un seul projet et les mêmes données

Problématique

Toujours en plein dans Django, mon projet actuel est de refaire tous mes sites web hébergés sur divers domaines en Django. Jusque là tout va bien je dirais. Ça se complique un peu quand l'idée est d'utiliser le même projet pour gérer tous ces sites web.

Pourquoi utiliser le même projet ? Plusieurs (bonnes) raisons à cela :

  • simplifier le développement, la maintenance et le déploiement : un projet à gérer c'est mieux que plusieurs
  • partager les mêmes données : c'est à mon avis le point essentiel qui m'intéresse : pouvoir créer du contenu (billet de blog, photo,...) et pouvoir facilement l'affecter à tel ou tel site ou à plusieurs à la fois (cela dépend également des applications, cf plus bas)
  • tout administrer de façon centralisée : c'est la suite du point précédent : pouvoir, dans une interface d'administration unique, créer du contenu et l'affecter à tel ou tel site web sans devoir changer d'interface d'admin

Je vais donc vous proposer ici la solution que j'ai retenu pour servir 2 domaines différents avec Django. Ceux-ci seront pour l'exemple www.domaine1.tld et www.domaine2.tld (ouaouh ! que c'est original ! - ça marche bien évidemment pour un sous-domaine...).

L'historique de cette configuration est expliqué en bas de ce billet pour ceux que ça intéresse.

note importante : puisqu'on m'a signalé que la solution présentée ici n'est pas forcément "propre" au sens Django puisqu'elle implique de changer le nom de divers fichiers de base d'un projet Django, et puisqu'après réflexion je suis d'accord avec cette remarque... voici quelques éclaircissements :

Si vous faites les choses le plus possible dans le style Django, en pensant application réutilisable il est en effet préférable de penser chaque domaine comme un projet séparé et donc d'isoler les fichiers plutôt de cette façon

www_domaine1_tld/
                 __init__.py
                 manage.py
                 settings.py
                 urls.py

www_domaine2_tld/
                 __init__.py
                 manage.py
                 settings.py
                 urls.py

ce qui permet de ne pas modifier les fichiers manage, settings et urls. Cela est encore facilité si votre projet est composé d'applications réutilisables, installables à la manière de modules Python (ce qui doit d'ailleurs être au maximum le cas).

La documentation ci-dessous est donc toujours valable mais il est probablement préférable d'utiliser cette manière de faire... à vous de voir maintenant...

Arborescence

Le premier point à voir est l'organisation des fichiers dans le répertoire du project. Afin de pas trop encombrer celui-ci, les fichiers ayant la même fonction (les fichiers settings.py, urls.py, les templates) sont déplacés dans des sous-répertoires, les fichiers manage.py restant eux à la racine, comme suit :

monprojet/
          www_domaine1_tld_manage.py
          www_domaine2_tld_manage.py
          settings/
                   __init__.py
                   www_domaine1_tld.py
                   www_domaine2_tld.py
                   global_settings.py
          urls/
               __init__.py
               www_domaine1_tld.py
               www_domaine2_tld.py
          templates/
                    www_domaine1_tld
                    www_domaine2_tld

il ne faut pas oublier les fichiers __init__.py aux endroits indiqués.

Fichiers manage.py

Pour exécuter des commandes Django, on peut utiliser $ django-admin.py <commande> <options> avec certaines contraintes (spécifier le fichier settings...) ou le "raccourci" $ ./manage.py <commande> <options>.

Dans le cas où l'on veut gérer plusieurs domaines, et où le fichier "settings" ne s'appelle pas settings.py, il faut créer un fichier manage.py différent par domaine.

Exemple avec www_domaine1_tld_manage.py.

#!/usr/bin/env python
from django.core.management import execute_manager
try:
    import settings.www_domaine1_tld
except ImportError:
    import sys
    sys.stderr.write("Error: Can't find the file 'settings.www_domaine1_tld.py' in the directory containing %r...." % __file__)
    sys.exit(1)

if __name__ == "__main__":
    execute_manager(settings.www_domaine1_tld)

Il suffira alors de lancer une commande comme la suivante pour exécuter des commandes Django sur un certain domaine

$ ./www_domaine1_tld_manage.py <commande> <options>

mise à jour : vu qu'on me l'a signalé, cette manipulation concernant les fichiers manage.py n'est pas nécessaire puisqu'il suffit d'ajouter l'option --settings=settings.www_domaine1_tld pour pouvoir utiliser ./manage.py de manière classique

Fichiers urls.py

Pour les fichiers urls, il suffit de créer les fichiers suivant l'arborescence indiquée au-dessus en utilisant des fichiers urls.py "classiques", avec la différence que si vous devez importer les "settings" du domaine il faut indiquer le module sous la forme settings.www_domaine1_tld

Framework "sites" de Django

Si vous voulez utiliser les mêmes applications sur plusieurs domaines sans pour autant devoir publier le même contenu partout, il faut alors utiliser des applications qui utilisent le framework "sites".

Vous devez dans ce cas :

  • créer des sites dans la base de données
  • et spécifier le SITE_ID qui va bien dans le fichier "settings" (voir plus bas)

Fichiers settings.py

Pour gérer les paramètres de chaque domaine/site on aura un fichier pour chacun ainsi qu'un fichier pour les paramètres globaux (pour les bases de données, les applications installées...).

Voici les paramètres spécifiques à chaque domaine, avec des commentaires.

# paramètres Django pour le site www.domaine1.tld

import os

# on charge ici les paramètres spécifiés dans global_settings.py
from global_settings import *

# on initialise BASE_DIR avec le répertoire de base du projet,
#  pour l'utiliser pour d'autres paramètres
BASE_DIR = os.path.join(os.path.dirname(__file__), '..')

# il faut indiquer ici l'identifiant du site (au sens Django), ansi que dans la base de données
# cf http://docs.djangoproject.com/en/dev/ref/contrib/sites/
SITE_ID = 1

# chemin absolu vers le répertoire des médias du domaine
MEDIA_ROOT = os.path.join(BASE_DIR, 'static', 'www_domaine1_tld')

# URL pour gérer les médias venant de MEDIA_ROOT
MEDIA_URL = 'http://www.domaine1.tld/static/'

# il faut ici initialiser une clé secrète distincte pour chaque domaine
SECRET_KEY = 'xxx'

# on spécifie ici le fichier urls.py
ROOT_URLCONF = 'monprojet.urls.www_domaine1_tld'

# on spécifie ici le répertoires des templates
TEMPLATE_DIRS = (
    os.path.join(BASE_DIR, 'templates', 'www_domaine1_tld')
)

# ce qui suit n'est pas obligatoire mais permet d'avoir un fichier spécifique
# en local afin par exemple de surcharger quelques paramètres pour le dev
try:
     from local_settings import *
except ImportError:
     pass

Et voici un exemple de fichier global_settings.py avec les paramètres qui peuvent/doivent être partagés.

DATABASE_ENGINE = ''
DATABASE_NAME = ''
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
)

Serveur web/Python/WSGI

Pour cette partie, j'utilise pour ma part (pour l'instant) Apache+mod_wsgi mais je ne vais pas rentrer dans les détails puisque ça n'est pas spécifique au cas particulier qui nous intéresse ici.

J'ai un fichier pour Apache qui indique un VirtualHost pour chaque domaine et pointe vers un fichier WSGI pour chacun également.

Dans le fichier WSGI il faut faire attention à étendre le PYTHONPATH si nécessaire et indiquer le fichier settings correctement, un peu comme ce qui suit :

import sys

sys.path.append('/opt/django/monprojet/')
os.environ['DJANGO_SETTINGS_MODULE'] = 'monprojet.settings.www_domaine1_tld'

Historique

Au début de ma recherche sur cette problématique je me suis tout de suite dit que c'était le boulot de Django de faire ça, et quand j'ai compris que ce n'était pas le cas naturellement, je me suis orienté vers des applications qui, étendant Django, lui permettait de gérer cela. J'ai trouvé deux applications dont le but semble de combler ce manque :

Je ne me souviens plus pourquoi j'ai mis de côté django-multisite mais mes tests ont porté sur django-threaded-multihost. L'application faisait ce pourquoi elle était prévu mais avait un problème majeur : il n'y avait qu'un settings.py, un urls.py... pour tous les domaines servis et là je me suis rendu compte que ce n'était pas vraiment ce dont j'avais besoin puisque le but était de pouvoir servir le même contenu mais bien évidemment de façon différente... Retour à la case départ !

Heureusement je suis tombé sur une discussion sur IRC #django-fr entre david`bgk et cyberdelia concernant ce thème et cyberdelia m'a alors expliqué comment ses divers domaines (pour http://www.croisedanslemetro.com il me semble (j'aime le concept de ce site :) soit dit en passant) pointant vers un même projet étaient gérés... c'était ce qu'il me fallait ! Le temps que ça mûrisse, celui de m'y replonger, d'implémenter la solution et de la tester, je me suis dit qu'il serait intéressant de poster cette solution et me voilà donc... (en ajoutant le temps de rédaction de ce billet ;-)... j'espère que ça pourra être utile à d'autres.

mercredi, juin 3 2009

Mercurial : éditer l'historique d'un dépôt (les changesets) avec les MQ

J’ai toujours[1] cru qu’on ne pouvait éditer l’historique des ‘changesets’ de Mercurial, mais en fait si ! Et c’est grâce à ce billet sur le blog de Jesper Noehr (un des gars derrière bitbucket) que j’ai découvert qu’en fait c’est tout à fait possible.

En utilisant les MQ (mercurial queues) dont je vous ai déjà parlé, il est donc tout à fait possible de rééditer des modifications déjà enregistrées (committées) dans votre dépôt Mercurial.

Bien évidemment, ce genre de manipulations n’est possible que si vous contrôlez votre dépôt et ses éventuels clones. Dès l’instant où celui-ci a pu être cloné, vous avez perdu la maîtrise de votre code et les modifications sur lesquelles vous voulez revenir sont déjà parties.

Nous sommes dans un dépôt test avec 3 changesets

$ hg log
changeset:   2:6a2d12a15cda
tag:         tip
summary:     modifications de a et b

changeset:   1:ca5faf3b4493
summary:     ajout de b

changeset:   0:66545c7be018
summary:     ajout de a

et nous souhaitons revenir sur les changesets 1 et 2.

Nous initialisons d’abord un dépôt de MQ si ce n’est pas déjà fait

$ hg qinit -c

[2]

Puis nous importons les changesets que nous voulons modifier

$ hg qimport -r 2:1

Si on regarde maintenant le log

changeset:   2:6a2d12a15cda
tag:         qtip
tag:         2.diff
tag:         tip
summary:     modification de a et b

changeset:   1:ca5faf3b4493
tag:         1.diff
tag:         qbase
summary:     ajout de b

changeset:   0:66545c7be018
tag:         qparent
summary:     ajout de a

on retrouve bien nos 3 différents changesets sauf que les 2 derniers sont différents : ce sont maintenant des patchs sous forme MQ que nous pouvons alors manipuler de façon classique[3]

On peut donc dépiler tous les patchs pour revenir dans l’état qu’on voulait avant les changesets 1 et 2

$ hg qpop -a
Patch queue now empty
$ hg log
changeset:   0:66545c7be018
tag:         tip
summary:     ajout de a

On peut alors à coup de hg qpush/hg qpop empiler/dépiler nos patchs afin de les modifier, les réorganiser ou ajouter des changesets, et donc revenir sur l’historique de notre dépôt.

J’ai découvert qu’en fait cette information est également disponible sur le site de Mercurial, voyez http://www.selenic.com/mercurial/wi…

Notes

[1] “toujours” est peut-être un trop grand mot, ça ne fait pas non plus si longtemps que ça que je connais Mercurial ;-)…

[2] tant qu’à faire nous ajoutons -c pour avoir un dépôt MQ ‘versionnable’

[3] je vous renvoie vers les chapitre 12 et 13 du hgbook pour plus d’infos

vendredi, décembre 12 2008

Mercurial : partager votre dépôt de patches MQ en même temps que votre dépôt principal

Travaillant actuellement sur un projet de développement en mode collaboratif, nous utilisons Mercurial pour gérer nos sources, et je me suis mis à utiliser intensément les MQ (Mercurial Queues) pour gérer mes propres modifications.

Je ne vais pas rentrer dans les détails de ce que sont les MQ, juste vous dire que c’est un système permettant de gérer une série de patchs “flottants”, un peu comme des commits mais que vous pouvez dépiler et empiler pour les modifier suivant vos besoins, tout en suivant le développement principal.
Je vous renvoie vers la documentation officielle et le chapitre qui y est consacré dans le livre (à lire et relire pour comprendre le principe).

Jusque là en travaillant de mon côté avec des MQ, je pouvais tranquillement poursuivre plusieurs développements en parallèle en local, tout en suivant le développement sur la branche principale. Quand un patch était ok, je l’appliquais sur le dépôt principal et tout allait bien.
Le problème se pose maintenant parce que je veux, tout en maintenant une série de patches, pouvoir les partager afin de montrer l’avancée de mon travail. (tout en continuant à pouvoir les empiler/dépiler bien évidemment)
C’est possible mais pas très pratique : ça demande de réinitialiser le dépôt côté serveur et les numéros de changesets changent, sans parler de l’obligation de passer par un dépôt local tiers.

Je me suis donc mis à chercher de la doc sur le partage de patches MQ et suis tombé sur ce très intéressant article intitulé Using Mercurial Queues and bitbucket.org[1] et, ne voulant pas utiliser bitbucket pour stocker mon dépôt[2], me suis mis en tête de faire quelque chose d’équivalent sur mon propre hébergement.

Peu de doc existe donc voici la mienne…

Sur le serveur

on commence par initialiser un dépôt qu’on va appeler test

$ hg init /chemin/vers/test

puis on y initialise un dépôt de MQ (qui l’on retrouvera dans .hg/patches)

$ cd /chemin/vers/test
$ hg qinit -c

on doit ensuite créer un fichier hgrc afin de spécifier les autorisations de chacun des deux dépôts : /chemin/vers/test/.hg/hgrc et /chemin/vers/test/.hg/patches/.hg/hgrc : je ne rentre pas dans les détails et vous renvoie vers mon article dédié au partage de dépôts Mercurial si vous en avez besoin.

Il faut également ajouter ces deux dépôts dans la configuration hgweb(dir).cgi

[paths]
test  =  /chemin/vers/test/
test-mq = /chemin/vers/test/.hg/patches

Si vous allez sur l’url de votre site vous devriez alors voir quelque chose comme ça Capture-Mercurial_repositories_index_-_Iceweasel.png

Sur le client

Clone

on clone le dépôt en précisant qu’on veut également le dépôt de MQ grâce à la commande dédiée qclone

$ hg qclone http://hg.domain.tld/test

Push

quand on veut pusher nos modifications sur le dépôt en y incluant notre dépôt de patches, il faut

  • d’abord dépiler tous les patches
$ hg qpop -a
  • ensuite pusher le dépôt principal
$ hg push https://hguser@hg.domain.tld/test
  • puis le dépôt MQ
$ hg push https://hguser@hg.domain.tld/test-mq

Pull

pour mettre à jour notre dépôt local avec les modifications disponibles sur le serveur, il faut procéder de la même façon que pour le push avec la commande pull : dépiler les patchs, mettre à jour chacun des deux dépôts.

Voilà, je crois que je n’ai rien oublié, n’hésitez pas à me le dire si c’est le cas.

Notes

[1] je reparlerais peut-être d’ailleurs à une autre occasion de bitbucket mais pour faire simple c’est une sorte de github à la sauce Mercurial

[2] principalement parce que pour l’instant le développement est fermé, quand il s’ouvrira bitbucket pourra être une solution intéressante

mercredi, janvier 30 2008

Mercurial : partage de dépôts différents en http et https + push par https

J’ai commencé à regarder du côté des SCM[1] (marre de la gestion CPOLD ;-) il y a quelque temps déjà, et m’était tout d’abord arrêté sur Subversion [2] étant novice en la matière. Il s’est vite révélé peu adapté à ma pratique : “développement” sur plusieurs machines pas toujours online, et pas de serveur dédié toujours en ligne pour stocker mes dépôts. Depuis peu, ce dernier problème s’est réglé (merci Gandi Hébergement[3]) mais pour autant, j’ai abandonné Subversion pour Mercurial, un gestionnaire de code source distribué.

Je ne vais pas entrer ici dans les détails sur les caractéristiques d’un gestionnaire de code source distribué, ses avantages, ou même sur une présentation détaillée sur Mercurial et son mode de fonctionnement (je vous laisse visiter le site officiel pour cela). Je vais uniquement présenter des détails sur la configuration que j’ai mise en place afin de parvenir à partager aisément des dépôts différents via http et https, ainsi que comment faire pour autoriser la mise à jour de ceux-ci via https.


présentation des dépôts Mercurial sur le web


Imaginons que vos dépôts soient stockés dans /mercurial et que vous vouliez pouvoir donner un accès en lecture sur le web, à l’adresse http://hg.domain.tld. Nous allons configurer Apache et Mercurial pour ce faire.

Tout d’abord, nous allons créer un emplacement pour les fichiers web et le script CGI en Python pour Mercurial. Prenons /var/www/domain.tld/hg. Créons à cet endroit un répertoire hgweb dans lequel nous allons placer le script hgwebdir.cgi que vous pouvez trouver sur votre système (sous Debian /usr/share/doc/mercurial/examples/hgwebdir.cgi) ou sur le site de Mercurial. Éditez ce fichier pour avoir à la fin :

def make_web_app():
    return hgwebdir("/etc/mercurial/hgweb.config")

Nous allons ensuite créer le fichier /etc/mercurial/hgweb.config avec un contenu comme suit :

[paths]
depot1  =  /mercurial/depot1
depot2  =  /mercurial/depot2

On peut également utiliser la directive [collections] pour partager une hiérarchie de dépôts mais nous partons du principe que l’on veut contrôler précisément ce que l’on partage.

À partir de là, il nous reste à configurer Apache. Créez un fichier /etc/apache2/sites-available/hg.domain.tld afin de déclarez votre VirtualHost. Voici en exemple mon fichier

NameVirtualHost *:80

<VirtualHost *:80>
    ServerAdmin webmaster@hg.domain.tld

    ServerName hg.domain.tld  

    DocumentRoot /var/www/domain.tld/hg

    <Directory /var/www/domain.tld/hg/>
        Options FollowSymLinks +ExecCGI
        AddHandler cgi-script .cgi
        DirectoryIndex hgweb/hgwebdir.cgi
        AllowOverride None

        RewriteEngine on
        RewriteBase /hgweb
        RewriteRule ^$ hgwebdir.cgi  [L]
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule (.*) hgwebdir.cgi/$1  [QSA,L]
    </Directory>

    LogLevel warn
    ErrorLog /var/log/apache2/hg.domain.tld-error.log
.   CustomLog /var/log/apache2/hg.domain.tld-access.log combined
    
    ServerSignature Off
</VirtualHost>

Notez ici les règles de réécriture qui permettent de faire disparaître hgwebdir.cgi de vos URL. C’est ce qui m’a pris le plus de temps à trouver avant de tomber sur cette doc.

Activez votre nouveau “site” ainsi que les modules Apache dont vous aurez besoin

# a2ensite hg.domain.tld
# a2enmod mod_python
# a2enmod rewrite
# /etc/init.d/apache2 restart

Vous pouvez maintenant aller voir http://hg.domain.tld et parcourir vos dépôts Mercurial depot1 et depot2


partager des dépôts différents par https


Tout ça, c’est très bien, mais on aimerait également pouvoir avoir accès à certains dépôts que l’on ne veut pas donner en lecture à tout internet : des dépôts privés en somme. Nous allons donc partager ceux-ci sur un site sécurisé avec authentification.

Nous allons donc retourner dans notre répertoire /var/www/domain.tld/hg/hgweb, copier hgwebdir.cgi en hgwebdirssl.cgi et éditez ce fichier pour avoir :

def make_web_app():
    return hgwebdir("/etc/mercurial/hgwebssl.config")

Créons le fichier /etc/mercurial/hgwebssl.config avec :

[paths]
depot1  =  /mercurial/depot1
depot2  =  /mercurial/depot2
depot_prive3 = /mercurial/depot3
depot_prive4 = /mercurial/depot4

Et retournons éditer /etc/apache2/sites-available/hg.domain.tld pour y ajouter :

# en haut du fichier
NameVirtualHost *:443

# à la fin du fichier
<VirtualHost *:443>
    ServerAdmin webmaster@hg.domain.tld

    ServerName hg.domain.tld

    DocumentRoot /var/www/domain.tld/hg

    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/hg.domain.tld.pem
    SSLCertificateKeyFile /etc/apache2/ssl/hg.domain.tld.key

    <Directory /var/www/domain.tld/hg/>
        Options FollowSymLinks +ExecCGI
        AddHandler cgi-script .cgi
        DirectoryIndex hgweb/hgwebdirssl.cgi
        AllowOverride None

        RewriteEngine on
        RewriteBase /hgweb
        RewriteRule ^$ hgwebdirssl.cgi  [L]
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule (.*) hgwebdirssl.cgi/$1  [QSA,L]

        AuthUserFile /etc/mercurial/hgweb.htpasswd
        AuthGroupFile /dev/null
        AuthName "hg.domain.tld Mercurial repository"
        AuthType Basic
#       <LimitExcept GET OPTIONS>
                Require valid-user
#       </LimitExcept>
    </Directory>

    ErrorLog /var/log/apache2/hg.domain.tld-error.log

    LogLevel warn

    CustomLog /var/log/apache2/hg.domain.tld-access.log combined
    ServerSignature Off

</VirtualHost>

On retrouve ici les directives rewrite que nous avons utilisé dans la première partie, à la différence qu’elles pointent vers le CGI spécifique à notre configuration SSL, et quelques directives spécifiques justement à https/SSL. Bien évidemment, il vous faudra créer un fichier de mots de passe (nommé ici hgweb.htpasswd) avec :

# htpasswd -c /etc/mercurial/hgweb.htpasswd hguser

pour autoriser l’utilisateur hguser.

Notez les directives <LimitExcept GET OPTIONS> qui, si elles sont décommentées, permettent à tout le monde de visualiser les dépôts tout en demandant une authentification pour interagir via les commandes de Mercurial (clone, pull, push…).

Rechargez la configuration d’Apache et regardez la différence entre http://hg.domain.tld et https://hg.domain.tld


autoriser la mise à jour des dépôts via https


Pour autoriser le push via https, une fois le travail ci-dessus effectué, il suffit d’éditer la configuration des dépôts en modifiant le fichier .hg/hgrc de chaque projet. Éditez-le ou créez-le et insérez les lignes suivantes :

[web]
allow_push = hguser


utilisation


Voilà c’est fait, vous pouvez maintenant interagir très simplement avec vos dépôts Mercurial. Pour cloner un dépôt public

$ hg clone http://hg.domain.tld/depot1

Pour cloner un dépôt privé

$ hg clone https://hguser@hg.domain.tld/depot_prive3

Pour mettre à jour (push) ce même dépôt

$ hg push https://hguser@hg.domain.tld/depot_prive3

J’ajouterais que le plus dur est de gérer finement les droits d’accès aux divers fichiers nécessaires au bon fonctionnement de cette configuration. Pour faire simple, comme d’habitude, laissez le minimum de droits sur les fichiers de configuration (appartenance à l’utilisateur www-data et lecture pour lui), et pour les dépôts j’ai choisi la solution un groupe devel pour mon utilisateur principal ainsi que www-data et les droits pour ce groupe.

mise à jour : il semble nécessaire (depuis les dernières mises à jour de Mercurial ?) d’ajouter une ligne baseurl = dans la section [web] des fichiers .hg/hgrc afin de cacher les hgweb(dir).cgi dans les url.

Notes

[1] définition en français et en anglais

[2] cf cet article

[3] je sais que je n’ai pas parlé de cette nouvelle offre très intéressante de Gandi le registrar bien connu (pour sa qualité de service, son support et son engagement) alors allez voir le bar de gandi

jeudi, novembre 23 2006

petit script Python pour récupérer des listes de voisins last.fm

Pour ceux qui ne connaissent pas encore Last fm : (présentation rapide) c'est une communauté pour tous ceux qui aiment et écoutent de la musique. Vous vous créez un compte, vous configurez votre lecteur de musique favori avec ce compte, et ensuite ce que vous écoutez apparait sur votre profil last.fm... voilà pour le début, ensuite vous pouvez tagger les morceaux, artistes écoutés, rejoindre ou créer des groupes, écrire des journaux, découvrir de la musique grâce à vos voisins, et grâce aux radios disponibles sur le site....... bref, plein de possibilités mais je vous conseille d'aller lire la FAQ si vous voulez plus d'informations.

Tout ça pour dire que ça fait déjà un an et demi que je m'y suis inscrit, que j'ai déjà "scrobblé" près de 5000 morceaux, que j'ai quelques "amis" et une multitude de voisins, que je suis abonné à 63 groupes exactement mais que c'est en constante évolution[1] et que j'ai voulu essayer de voir si sur un groupe donné, on retrouvait les mêmes voisins parmi les membres. J'ai donc écrit un petit script Python qui, même si il n'est pas parfait (cf TODO) a le mérite de faire ce qu'on lui demande.

#! /usr/bin/python -tt

## TODO ##
# recuperation de la liste des membres d'un groupe
# rendre le script portable en enlevant l'appel a wget

import os
import re

# liste à compléter
membres = ['gal33Za']

for membre in membres:
    os.system('wget http://ws.audioscrobbler.com/1.0/user/%s/neighbours.txt -O voisins-%s'%(membre,membre))

fichiers = ['voisins-'+x for x in membres ]

voisins = {}

for fichier in fichiers:
    f = open(fichier, "r")

    for line in f.readlines():
        v, voisin = line.split(",")
        voisin = voisin[:-1]
       
        if voisin in voisins:
            voisins[voisin] = voisins[voisin] + 1
        else:
            voisins[voisin] = 1
    #endfor
   
    f.close
#endfor


# pour afficher avant tri
#for key, value in voisins.iteritems():
#   print str(value) + " : " + str(key)

### tri de dictionnaire
### cf    http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52306

voisinsRanges = sorted(voisins.items(), key=lambda (k,v): (v,k), reverse=True)

for voisin in voisinsRanges:
    nom, nombre = voisin;

    # pour générer une liste simple
    #print str(nom) + " : " + str(nombre)

    # pour générer du BBcode
    print "[url=http://www.last.fm/user/" + str(nom) + "]" + str(nom) + "[/url] : " + str(nombre)
   
    # pour générer du HTML
    #print "<a href=\"http://www.last.fm/user/" + str(nom) + "\">" + str(nom) + "</a> : " + str(nombre)
#endfor

# endscript

ps : je ne sais pas si ça vous a intéressé, mais en tout cas, ça prouve que je ne suis pas mort...

Notes

[1] le nombre de groupes existants est énorme et il y en a pour tous les goûts : des musicaux (Post-Rock Instrumental, Godspeed You! Black Emperor, radiohead, Mogwai, Noir Desir, Constellation Records, Indie and Alternative...), des géographiques (France, EU), des "geeks" (Amarok Users, Debian Linux, jabber, Vi (oui, le bon vieux troll vi vs emacs se retrouve également sur last.fm), des "politiques" (I Still Buy CDs, Music fans and Musicians against the RIAA, I don't have an iPod, Shove your DRM up your ass, NO 2 ID, I Hate Football) ou conceptuels dont certains dont le nom seul oblige à s'inscrire (I Don't Mean To Come Off As A Music Snob, I Grew Up In A Small Town and Suprisingly I Listen to Good Music!, Non aux Red Hot en tête de Charts, , The Black Background Users, People who join too many groups) etc, etc, etc

dimanche, septembre 24 2006

développement Python

J’ai récemment eu envie de me remettre au développement, ayant 2-3 projets dans le coin de ma tête. Et je me suis dit que j’allais me mettre à Python, langage interprété, multiplateforme et libre bien évidemment. C’est un peu le langage à la mode en ce moment[1] et après être tombé sur moults articles en traitant[2] et projets l’utilisant[3], je me suis décidé.
Je me suis également intéressé à la problématique de gestion de version pour gérer un projet informatique, surtout à plusieurs. CVS en est le représentant historique dans le monde du logiciel libre, mais depuis peu une foule de nouveaux outils sont apparus[4], et après un bref passage du côté de Mercurial, je me suis finalement décidé sur Subversion dont l’approche et le mode centralisé sont plus simples à appréhender.

Voici donc une série de ressources que j’ai retenu pour le développement en Python et l’utilisation de Subversion:

Vous en voulez plus ? allez voir l’article Cours pour apprendre Python sur biologeek.org pour plus de liens.

  • les outils ensuite :
    • Eclipse est un IDE écrit en Java et initialement prévu pour ce langage, mais qui à l’aide de nombreux plugins s’adaptent à d’autres langages. Essayez-le et appréciez;
    • Pydev est le plugin pour développer en Python avec Eclipse, et une doc;
    • Subclipse quant à lui est le plugin pour travailler avec Subversion;
    • la société Edgewall met à disposition un très bon outil pour gérer un projet basé sur Subversion, c’est Trac[6], mais elle propose également une Python Sidebar pour avoir dans votre navigateur favori la référence Python directement accessible.

J’ai parcouru ensuite le web de tutoriel en tutoriel pour réussir à installer de façon satisfaisante Subversion + Trac sur ma Debian mais ceux que j’ai trouvé marchaient plus ou moins bien[7] , jusqu’à celui-ci Installer et configurer Apache2, Trac et Subversion sur Ubuntu sur Prendre Un Café. Il concerne Ubuntu mais marche tout aussi bien pour Debian testing/etch.

update : je viens à peine de finir ce billet que je tombe sur un tuto Eclipse pour le développement web, PHP et Python sous Dapper. Pour Debian, j’ai eu des problèmes pour installer un plugin (Pydev il me semble) parce que la dernière version proposée était la 3.1 et qu’il me fallait la 3.2.

Notes

[1] mais j’aurais également pu choisir Ruby pour cette même raison :)

[2] dont Pourquoi programmer en Python ? sur biologeek.org

[3] dont Xen, ou tout au moins son interface d’administration, n’est pas le moindre

[4] voir une liste

[5] apt-get install diveintopython

[6] outil proposant un Wiki, un browser du code, un gestionnaire de tickets,… Il est utilisé par Dotclear notamment

[7] je parle de ceux de destination-linux.org, PrO²jects, xhtml.net, ou le site de Trac. Ils sont peut-être corrects mais je les ai trouvés moins directs que celui de Prendre Un Café…

mercredi, mai 17 2006

Java devient open source

Il semblerait que cette fois ça y est, Sun a décidé de rendre Java Open Source. C'est le nouveau PDG Jonathan Schwartz qui vient de l'annoncer mais pour l'instant les détails restent inconnus.
Cela faisait bien longtemps que la communauté open source et les développeurs Java le demandaient afin de contrer l'offre alternative : .Net de Microsoft. Dernièrement, IBM en avait ajouté une couche en demandant également cette libération du code de Java.
Peut-être qu'enfin, on a vu chez Java que cette demande était fondée, et que ça ne pouvait que bénéficier à Java[1] face à des alternatives comme .Net bien sûr, mais également face à la montée en puissance de PHP, Python, et Ruby (avec Ruby on Rails) qui sont les stars open source du moment dans le développement (tout au moins web) (cf par ex. cette article de 01 sur Ruby on Rails)

En tout cas, c'est un 'must' des trolls sur linuxfr.org qui tombe... le fameux "Java, çapuecpaslibre !" :-)

Et en plus, Sun prévoit de supporter Ubuntu sur sa ligne de serveur T1... que de changements..

[via Linox.be ici & ]

Notes

[1] je l'espère en tout cas

vendredi, avril 21 2006

marre de l'internet social ? (et autres...)

Si vous aussi vous en avez marre de ce nouvel internet social, de ces communautés qui se forment sur tout et n'importe quoi (les goûts musicaux, l'origine géographique, le navigateur web utilisé, votre pointure, etc.), de ce "web 2.0", de cette idée selon laquelle "l'internet sera social ou ne sera pas"[1], alors dans ce cas, isolatr.com est fait pour vous...

Si vous vous demandez depuis toujours ce que sont ces 'foo', 'bar, 'foobar' et autres 'baz', 'qux', 'quux'..., que l'on croise de temps en temps dans les docs informatiques en langue anglaise, alors allez consulter cette RFC (qu'est-ce qu'une RFC ?)

Si les méthodes Merise, RAD ou Extreme Programming vous sont déjà connues, mais ne vous ont pas satisfaites, vous pouvez toujours essayer la méthodologie de La Rache, bien connue des meilleures universités informatiques.

[via Michael Noll et Alexandre Dulaunoy à l'origine de GooDiff (cf billet précédent)]

Notes

[1] euh, cherchez pas, c'est de moi ! :)