By Ingeniweb. A Django site.
Juillet 25, 2008
» Faire du virtualhosting avec zope façon wsgi

J'en avais ras le bol que Zope nécessite des url complètement tordues pour faire du virtual hosting. Ça m'empêchais entre autre d'utiliser Paste#urlmap pour dispatcher certaines url sur d'autres applis que Zope.

Du coup, j'ai tenté un truc tout con: plutôt que d'utiliser les RewriteRule d'Apache, récrire le PATH_INFO en englobant l'application Zope dans une autre. Et ça marche. Fiesta !

Voilà donc à quoi ça ressemble. J'utilise zopeproject. J'ai donc modifier le machin qui créer l'application Zope. A savoir le fichier startup.py comme ceci:

def application_factory(global_conf, conf='zope.conf', vhost='www.gawel.org'):
    vhost = '/++vh++http:%s:80/++' % vhost
    zopeapp = zope.app.wsgi.getWSGIApplication(zope_conf)
    def zopewrapper(environ, start_response):
        environ['PATH_INFO'] = vhost + environ['PATH_INFO']
        return zopeapp(environ, start_response)
    return zopewrapper

Et hop, ça roule. L'avantage, en plus d'avoir une url propre en entrée, c'est que vu que je développe aussi derrière Apache, j'ai juste eu à changer mon fichier debug.ini pour prendre en compte mon virtual host de développement.

En fait j'ai fais un peu mieux que tout ça, car comme dit au début, le but était d'utiliser Paste#urlmap. La source de la bidouille en question est ici.

Aller, pendant que j'y suis, j'en chiais aussi pas mal pour déterminer vers quel backend rediriger les requêtes dans varnish. Tester des ++ dans l'url, ça lui plaisait pas du tout. Vu que j'utilise Apache devant (surtout pour subversion), j'ai trouvé le truc. Il suffit d'activer le module headers:

# a2enmod headers

Puis rajouter un truc du genre dans votre virtualhost Apache:

RequestHeader set VARNISH_BACKEND gawel_org

Vous l'aurez compris, ceci ajoute un header à la requête. Ensuite, dans varnish, on test ce header:

if (req.http.VARNISH_BACKEND ~ "gawel_org") {
    set req.backend = gawel_org;
}

Et le tour est joué. Il faut bien sur que toutes les requêtes entrantes aient ce header. Pour moi ce n'est pas un problème vu que tout passe par apache.

Mars 31, 2008
» svn: le futur du CMS !!!

Vous êtes comme moi je suis sûr. Editer des document dans des éditeur WYSIWYG, ça vous saoul au plus haut point. Personnellement, je préfère de loin un bon éditeur et du reStructuredText.

La solution je l'avais depuis longtemps. Stocker les billets de mon blog dans un svn et les publier avec un framework quelconque. Ca fait un moment maintenant que j'utilise Zope3 pour ça, mais le code était assez dégueulasse. Je me suis donc dit que si je mettais tout ça un peu au propre, cela pourrait servir à d'autres. Je me suis donc lancé dans un énorme week-end de geeking. le résultat est gp.svnfolder, un package Zope3 permettant de publier un répertoire svn contenant des documents en reStructuredText.

Voyons plutôt. Il nous faut une repository valide:

>>> import os
>>> from os.path import split
>>> curdir = split(os.getcwd())[0]
>>> repos = os.path.join(curdir, 'tests', 'rstfolder')

Avec ça, on peut instancier un dossier svn:

>>> from gp.svnfolder.folder import SVNFolder
>>> folder = SVNFolder()
>>> folder.__name__ = 'blog'
>>> folder.path = u'file://%s' % repos

Et en voir le contenu:

>>> sorted(folder.keys())
[u'doc.rst', u'doc2.rst']

Et accéder aux fichiers:

>>> file = folder['doc.rst']
>>> print file.data
Document 1
==========
<BLANKLINE>

Ensuite il suffit d'utiliser les vues fournies pour rendre ces fichiers en html.

Voila. Je penses qu'avec un peu de motivation je pourrais facilement le rendre compatible avec Mercurial et Bazaar mais ça suffit à faire mon bonheur :)

Mars 13, 2007
» Zope 3 adapters et widgets

Zope 3 adapters et widgets

Épris d'une étonnante motivation ces derniers jours, j'ai pondu deux tutoriels sur Zope 3. Un sur les adapters et un sur les widgets.

Ils sont aussi visible sur le site de l'AFPy, bien sûr !

» Zope 3 adapters et widgets

Épris d'une étonnante motivation ces derniers jours, j'ai pondu deux tutoriels sur Zope 3. Un sur les adapters et un sur les widgets.

Ils sont aussi visible sur le site de l'AFPy, bien sûr !

Mars 1, 2007
» Zope3 et XML avec lxml (suite)

Zope3 et XML avec lxml (suite)

Comme on le sait tous, écrire des expressions python dans des ZPT, c'est mal. J'ai donc voulu aller plus loin afin de pouvoir utiliser les tree xml sans avoir à faire des expressions python.

Avec Zope3, il est possible de rajouter des espaces de nom au expressions TAL (ah, les joies de la component architecture :). J'ai donc implémenté deux espaces de nom supplémentaire me permettant d'accéder aux méthodes find et findall d'un élément xml.

Ma template a donc maintenant cette jolie bouille:

<dl class="lastfm" tal:define="root view/xml">
  <dt class="title" tal:content="view/title" />
  <dd tal:repeat="track root">
      <a tal:attributes=" href track/find:url/text">
        <span tal:content="track/find:name/text" />
        <span class="small"
              tal:content="track/find:artist/text" />
      </a>
  </dd>
</dl>

Ce qui est tout de même plus agréable à regarder. Ce qui est déconcertant c'est la facilité d'implémentation. Il suffit de quelques lignes de code pour obtenir ce résultat.

» Zope3 et XML avec lxml (suite)

Comme on le sait tous, écrire des expressions python dans des ZPT, c'est mal. J'ai donc voulu aller plus loin afin de pouvoir utiliser les tree xml sans avoir à faire des expressions python.

Avec Zope3, il est possible de rajouter des espaces de nom au expressions TAL (ah, les joies de la component architecture :). J'ai donc implémenté deux espaces de nom supplémentaire me permettant d'accéder aux méthodes find et findall d'un élément xml.

Ma template a donc maintenant cette jolie bouille:

<dl class="lastfm" tal:define="root view/xml">
  <dt class="title" tal:content="view/title" />
  <dd tal:repeat="track root">
      <a tal:attributes=" href track/find:url/text">
        <span tal:content="track/find:name/text" />
        <span class="small"
              tal:content="track/find:artist/text" />
      </a>
  </dd>
</dl>

Ce qui est tout de même plus agréable à regarder. Ce qui est déconcertant c'est la facilité d'implémentation. Il suffit de quelques lignes de code pour obtenir ce résultat.

Février 26, 2007
» Zope3 et XML avec lxml

Zope3 et XML avec lxml

Ce soir j'ai joué avec Zope3 et lxml. Le résultat est plutôt intéressant. On trouve en combinant les ZPT et lxml une bonne alternative au xslt.

Voyez plutôt. Une petite classe de vue:

# -*- coding: utf-8 -*-
import os
import random
from lxml import etree
from zope.publisher.browser import BrowserView

PREFIX = '/tmp'

def getxml(filename):
    """
    return a ElementTree parsed from the file
    """
    fd = open(os.path.join(PREFIX,filename))
    doc = etree.parse(fd)
    fd.close()
    return doc.getroot()

class LastfmView(BrowserView):
    """
    a view to render the xml
    """

    views = ((u'Coup de coeur','recentlovedtracks.xml'),
             (u'Ecouté récemment','recenttracks.xml'))

    def __call__(self):
        self.title, filename = random.choice(self.views)
        self.xml = getxml(filename)
        return super(LastfmView,self).__call__()

Associée à une petite template toute bête:

<dl class="lastfm" tal:define="root view/xml">
  <dt class="title" tal:content="view/title" />
  <dd tal:repeat="track root">
      <a tal:attributes=" href python: track.find('url').text">
        <div tal:content="python: track.find('name').text" />
        <div class="small"
             tal:content="python: track.find('artist').text" />
      </a>
  </dd>
</dl>

Et hop, vous obtenez le portlet lastfm de mon site :)

Vous pouvez voir le source complet ici avec le script bash qui dowload les xml.

» Zope3 et XML avec lxml

Ce soir j'ai joué avec Zope3 et lxml. Le résultat est plutôt intéressant. On trouve en combinant les ZPT et lxml une bonne alternative au xslt.

Voyez plutôt. Une petite classe de vue:

# -*- coding: utf-8 -*-
import os
import random
from lxml import etree
from zope.publisher.browser import BrowserView

PREFIX = '/tmp'

def getxml(filename):
    """
    return a ElementTree parsed from the file
    """
    fd = open(os.path.join(PREFIX,filename))
    doc = etree.parse(fd)
    fd.close()
    return doc.getroot()

class LastfmView(BrowserView):
    """
    a view to render the xml
    """

    views = ((u'Coup de coeur','recentlovedtracks.xml'),
             (u'Ecouté récemment','recenttracks.xml'))

    def __call__(self):
        self.title, filename = random.choice(self.views)
        self.xml = getxml(filename)
        return super(LastfmView,self).__call__()

Associée à une petite template toute bête:

<dl class="lastfm" tal:define="root view/xml">
  <dt class="title" tal:content="view/title" />
  <dd tal:repeat="track root">
      <a tal:attributes=" href python: track.find('url').text">
        <div tal:content="python: track.find('name').text" />
        <div class="small"
             tal:content="python: track.find('artist').text" />
      </a>
  </dd>
</dl>

Et hop, vous obtenez le portlet lastfm de mon site :)

Vous pouvez voir le source complet ici avec le script bash qui dowload les xml.

Mai 13, 2006
» Zope3: comment ajouter un objet proprement.

Zope3: comment ajouter un objet proprement.

A première vue, le plus simple moyen d'ajouter un objet dans un dossier serait le suivant:

>>> from zope.app.folder import Folder
>>> mycontent = Folder()
>>> mycontent.__name__ = 'myid'

>>> folder = Folder()
>>> folder['myid'] = mycontent

Cela fonctionne bien. L'inconvénient est que l'on perds toute la machinerie de zope.

J'ai donc cherché un moyen plus propre d'ajouter un objet. J'en suis arrivé a la conclusion que le mieux est d'utiliser une vue Adding. Ce qui nous donne:

from zope.app.container.browser.adding import Adding
from zope.app.event.objectevent import ObjectCreatedEvent
from contents import MyContent

class MyContentView(BrowserView):
    """ a simple view """

    def add(self):
        view = Adding( self.context, self.request )
        context = view.add( MyContent() )
        notify(ObjectCreatedEvent(context))

Par ce moyen, on créer un objet proprement. Par exemple l'id seras généré à l'aide d'un INameChooser, etc.

» Zope3: comment ajouter un objet proprement.

A première vue, le plus simple moyen d'ajouter un objet dans un dossier serait le suivant:

>>> from zope.app.folder import Folder
>>> mycontent = Folder()
>>> mycontent.__name__ = 'myid'

>>> folder = Folder()
>>> folder['myid'] = mycontent

Cela fonctionne bien. L'inconvénient est que l'on perds toute la machinerie de zope.

J'ai donc cherché un moyen plus propre d'ajouter un objet. J'en suis arrivé a la conclusion que le mieux est d'utiliser une vue Adding. Ce qui nous donne:

from zope.app.container.browser.adding import Adding
from zope.app.event.objectevent import ObjectCreatedEvent
from contents import MyContent

class MyContentView(BrowserView):
    """ a simple view """

    def add(self):
        view = Adding( self.context, self.request )
        context = view.add( MyContent() )
        notify(ObjectCreatedEvent(context))

Par ce moyen, on créer un objet proprement. Par exemple l'id seras généré à l'aide d'un INameChooser, etc.

Mai 2, 2006
» Utilisation de INameChooser

Utilisation de INameChooser

Pour un blog, on veut pouvoir créer des post sans avoir à définir leurs ids.

On en fait de même pour les folders. Voici le principe:

  • Si nous sommes dans le dossier racine, on créé un dossier pour l'année.
  • Si on est dans une année, on créé un dossier pour le mois
  • Si on est dans un mois, on créé une post qui a son id basé sur le jour et l'heure courante.

Dans Zope cela donnes:

>>> from zope.app.container.interfaces import INameChooser
>>> from zope.app.container.contained import NameChooser
>>> import datetime

>>> class IWeblog(zope.interface.Interface):pass

>>> class WeblogNameChooser(NameChooser):
...     zope.interface.implements(INameChooser)
...     zope.component.adapts(IWeblog)
...     def __init__(self,context):
...         self.context = context
...     def chooseName(self,name,object):
...         container = self.context
...         now = datetime.datetime.now()
...         year = now.strftime('%Y')
...         month = now.strftime('%m')
...         if 'log' in str(container.__name__):
...             return unicode(year)
...         elif str(container.__name__) == year:
...             return unicode(month)
...         elif str(container.__name__) == month:
...             return unicode(now.strftime('%Y%m%d%H'))
...         return NameChooser.chooseName(self,name,object)

L'interface INameChooser définit un adapter qui permet de modifier la façon dont zope génère ses ids. Ici, on utilise la date et l'id du parent pour générer l'id de nos objets.

On peut tester que l'exemple fonctionne bien:

>>> now = datetime.datetime.now()
>>> year = now.strftime('%Y')
>>> month = now.strftime('%m')

On créé un folder root:

>>> from zope.app.folder import Folder
>>> folder = Folder()
>>> folder.__name__ = 'weblog'

On instancie notre INameChooser et on vérifie qu'il génère bien une année:

>>> chooser = WeblogNameChooser(folder)
>>> chooser.chooseName('',None) == unicode(year)
True

Même exemple. Mais ici, le chooser renvois le mois:

>>> folder.__name__ = unicode(year)
>>> chooser = WeblogNameChooser(folder)
>>> chooser.chooseName('',None) == unicode(month)
True

Bien que l'interface soit définit pour un dossier, elle est utilisée pour définir l'id de l'objet que l'on ajoute dans un dossier et non l'id du dossier lui même. C'est ainsi que l'on peut aussi définir l'id d'un post.

On peut donc aussi basé le choix de l'id sur le type de contenu que l'on ajoutes dans le dossier.

On voit ici la puissance des interfaces de Zope3 car il est possible par ce moyen de redéfinir la façon dont seras généré l'id de n'importe qul objet simplement en implémentant un NameChooser pour l'interface de ces objets.

» Utilisation de INameChooser

Pour un blog, on veut pouvoir créer des post sans avoir à définir leurs ids.

On en fait de même pour les folders. Voici le principe:

  • Si nous sommes dans le dossier racine, on créé un dossier pour l'année.
  • Si on est dans une année, on créé un dossier pour le mois
  • Si on est dans un mois, on créé une post qui a son id basé sur le jour et l'heure courante.

Dans Zope cela donnes:

>>> from zope.app.container.interfaces import INameChooser
>>> from zope.app.container.contained import NameChooser
>>> import datetime

>>> class IWeblog(zope.interface.Interface):pass

>>> class WeblogNameChooser(NameChooser):
...     zope.interface.implements(INameChooser)
...     zope.component.adapts(IWeblog)
...     def __init__(self,context):
...         self.context = context
...     def chooseName(self,name,object):
...         container = self.context
...         now = datetime.datetime.now()
...         year = now.strftime('%Y')
...         month = now.strftime('%m')
...         if 'log' in str(container.__name__):
...             return unicode(year)
...         elif str(container.__name__) == year:
...             return unicode(month)
...         elif str(container.__name__) == month:
...             return unicode(now.strftime('%Y%m%d%H'))
...         return NameChooser.chooseName(self,name,object)

L'interface INameChooser définit un adapter qui permet de modifier la façon dont zope génère ses ids. Ici, on utilise la date et l'id du parent pour générer l'id de nos objets.

On peut tester que l'exemple fonctionne bien:

>>> now = datetime.datetime.now()
>>> year = now.strftime('%Y')
>>> month = now.strftime('%m')

On créé un folder root:

>>> from zope.app.folder import Folder
>>> folder = Folder()
>>> folder.__name__ = 'weblog'

On instancie notre INameChooser et on vérifie qu'il génère bien une année:

>>> chooser = WeblogNameChooser(folder)
>>> chooser.chooseName('',None) == unicode(year)
True

Même exemple. Mais ici, le chooser renvois le mois:

>>> folder.__name__ = unicode(year)
>>> chooser = WeblogNameChooser(folder)
>>> chooser.chooseName('',None) == unicode(month)
True

Bien que l'interface soit définit pour un dossier, elle est utilisée pour définir l'id de l'objet que l'on ajoute dans un dossier et non l'id du dossier lui même. C'est ainsi que l'on peut aussi définir l'id d'un post.

On peut donc aussi basé le choix de l'id sur le type de contenu que l'on ajoutes dans le dossier.

On voit ici la puissance des interfaces de Zope3 car il est possible par ce moyen de redéfinir la façon dont seras généré l'id de n'importe qul objet simplement en implémentant un NameChooser pour l'interface de ces objets.