Fantomas’side

Weblog open-source

Ô middleware, mon beau middleware : Request Template Loader

Dans le monde du web, là où tout est anarchie (ou presque), je me retrouve confronté assez souvent à cette problématique :

Comment afficher le contenu d'un site réalisé avec Django sur un autre site déjà existant ?

Là où je bosse, on aime bien les iframes HTML (no comments please :)), mais le contenu ou le style de la frame incluse ne correspondent pas forcément au site recevant la frame. L'idée, vu qu'on se trouve à la base dans un environnement Django, serait de déployer un nouveau site, avec son propre jeu de templates prévues pour le site incluant les frames.

Mais ce genre de technique nécessite de mettre en place un nouveau domaine, de déployer et de maintenir 2 sites. Bon vous me direz que ce n'est pas forcément ce qu'il y a de plus dur, mais il y a moyen de faire mieux. Et surtout que ce passe-t-il si vous devez faire ce genre d'opération non pas sur 1 site, mais sur plusieurs. Tout de suite cela devient plus ennuyeux. :(

C'est là où intervient la solution du middleware. L'idée est de pouvoir passer en GET un paramètre qui conditionnera le chargement des templates dans le site source. Comme cela peu importe combien de frames au look différent on devra gérer, la mise en forme et la personnalisation du contenu se fera très facilement.

Maintenant place au code, les explications sur le fonctionnement viennent directement après.

import os
from urllib import urlencode
from urlparse import urlparse
from urlparse import urlunparse
try:  # For Python < 2.6
    from urlparse import parse_qs
except ImportError:
    from cgi import parse_qs

from django.conf import settings
from django.utils.encoding import smart_str

from BeautifulSoup import BeautifulSoup


GET_VAR_NAME = getattr(settings, 'TEMPLATE_GET_VAR_NAME', 'from_source')


class RequestTemplateMiddleware(object):
    """Middleware to modify on the fly
    the loading of the templates"""

    def __init__(self):
        self.original_template_dirs = settings.TEMPLATE_DIRS

    def process_request(self, request):
        source = request.GET.get(GET_VAR_NAME)

        if source:
            settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__),
                                                   'templates', source),) + \
                                                   settings.TEMPLATE_DIRS
        return None

    def process_response(self, request, response):
        settings.TEMPLATE_DIRS = self.original_template_dirs

        source = request.GET.get(GET_VAR_NAME)

        if source and '<html' in response.content:
            soup = BeautifulSoup(smart_str(response.content))
            for link in soup.findAll('a'):
                link['href'] = self.update_link(link['href'], source)
            for form in soup.findAll('form'):
                form['action'] = self.update_link(form['action'], source)
                form.insert(0, '<div><input type="hidden" name="%s" ' \
                            'value="%s" /></div>' % (GET_VAR_NAME, source))
            response.content = str(soup)

        return response

    def update_link(self, link, source):
        """Update the links with the good GET parameter"""
        url_parts = urlparse(link)
        query_dict = parse_qs(url_parts.query)
        query_dict.update({GET_VAR_NAME: source})
        return urlunparse((url_parts.scheme, url_parts.netloc,
                           url_parts.path, url_parts.params,
                           urlencode(query_dict), url_parts.fragment))

Comme on peut le voir, ce middleware va court-circuiter l'ordre de chargement des templates en modifiant la variable TEMPLATE_DIRS utilisée par django.template.loaders.filesystem.Loader, grâce à la variable from_source passée dans l'url en GET.

Ensuite il ne reste plus qu'à créer le dossier de templates correspondant à la variable from_source et d'y placer les templates personnalisés. A savoir que le nom de la variable passée en GET est configurable pour plus de souplesse.

La seule dépendance requise est BeautifulSoup qui va modifier le contenu HTML afin de pouvoir continuer à utiliser le middleware quand on clique sur un lien ou on valide un formulaire.

Au delà de l'utilité de ce bout de code, j'espère que cela va vous motiver à programmer et utiliser vos propres middlewares, car cela s'avère souvent la meilleure solution à un problème complexe lors du développement d'un site internet avec Django.