ka.da

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

Mot-clé - projets

Fil des billets - Fil des commentaires

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.

lundi, mars 2 2009

django-userthemes : une application pour gérer des thèmes utilisateur pour Django

Un petit billet pour annoncer la sortie de la version 0.1 de l’application django-userthemes. C’est une “application réutilisable” pour Django[1].

L’origine de cette application est le besoin de créer une système de thèmes pour un projet sur lequel je travaille. Après moultes recherches[2], j’ai décidé de créer ma propre implémentation en essayant au maximum de suivre cette philosophie de ‘reusable apps’.

django-userthemes permet donc de définir pour chaque utilisateur enregistré dans une application un thème favori qui sera chargé quand il se connectera à celle-ci. On peut définir le répertoire où seront stockés les thèmes et aussi celui par défaut qui sera chargé quand aucun utilisateur n’est connecté ou que sa préférence n’est pas fixée. Je vous encourage à lire la doc (et le code) pour comprendre comment ça fonctionne en détail.

Vous trouverez le projet à cette adresse : http://bitbucket.org/daks/django-us… où vous pourrez récupérer le code source (via Mercurial ou un fichier archive), rapporter des bugs…

La license utilisée en la GNU GPLv2.

Le projet est encore en phase de développement donc tout retour est bienvenue.

Si vous vous décidez à l’utiliser dans un de vos projets, merci de le dire en utilisant un widget ohloh comme celui-ci :

Notes

[1] voyez cette vidéo de James Bennett et cette convention de Eric Holscher pour plus d’infos

[2] me conduisant vers des projets morts ou ne correspondant pas à nos besoins/désirs : django-themes, django-skins, django-userskins, django-dbtemplates