svn: le futur du CMS !!!

Posté le 01/04/2008 sous zope3

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(split(os.getcwd())[0])[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 :)

Zope 3 adapters et widgets

Posté le 13/03/2007 sous afpy zope3 python

É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 !

Zope3 et XML avec lxml (suite)

Posté le 01/03/2007 sous zope3 python

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

Posté le 26/02/2007 sous zope3 python

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: comment ajouter un objet proprement.

Posté le 13/05/2006 sous zope3

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.

Utilisation de INameChooser

Posté le 02/05/2006 sous zope3

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.