Wilane' Weblog

jaZZ ... just another Zine Zblog

No group of professionals meets except
to conspire against the public at large.
-- Mark Twain

django.forms et datepicker

écrit par wilane, le 21 juin 2009 22:34:00.

jQuery dispose de pléthores de greffons avec une superbe façon de les packager depuis le site web avec les styles bien mariés au style de votre site. Parmi les greffons les plus célèbres on peut citer ui.effects.*, ui.accordeon, ui.dialog, ui.draggable, ui.droppable, ui.progressbar, ui.resizeable, ui.selectable, ui.slider, ui.sortable, ui.tabs et ui.datepicker.

La définition d’éléments de style avec Django est plutôt simple, et le deviendra encore plus si les travaux SoC 2009 de Zain Memon sur admin-ui venaient à être acceptés. Au niveau des éléments de formulaire, il suffit généralement de définir l’attribut `attrs' (dictionnaire) du widget que vous souhaitez associer à votre élément de formulaire:

#pour un champ date
forms.DateField(
            widget=forms.DateInput(attrs={'class':'datepicker'}, 
                                   format='%d-%m-%Y'),
            input_formats=['%d/%m/%Y %H:%M:%S',
                           '%d-%m-%Y %H:%M',
                           '%d-%m-%Y',
                           ],
            )

Une façon élégante d’affubler tous les champs de type date de l’attribut `class="datepicker"' est d’utiliser l’attribut de classe non documenté de la classe Form ou ModelForm `formfield_callback', par exemple:

def mon_datefield_perso(f):
    if isinstance(f, models.fields.DateField):
        return forms.DateField(
            widget=forms.DateInput(attrs={'class':'datepicker'}, 
                                   format='%d-%m-%Y'),
            )
    else:
        return f.formfield()

class MonForm(forms.ModelForm):
    formfield_callback = mon_datefield_perso
   
    class Meta:
        model = Monmodele

Une fois définit ce callback, il vous suffira d’utiliser dans votre template de base un appel générique à .datepicker() pour tous les éléments de la classe .datepicker:

$(function() {
    $('.datepicker').datepicker({inline: true});
});

La technique marche également pour un FormSet, *formset_factory utilise aussi ce type de callback (par défaut lambda f: f.formfield()) :

FactureFormSet = modelformset_factory(MonModele, 
                                      form=MonForm, 
                                      formfield_callback=mon_datefield_perso
                                     )

Migration de Schéma/Données

écrit par wilane, le 18 mai 2009 18:59:00.

La migration de données et de schéma dans le cadre d’un framework est une tâche difficile dans son implémentation. Il consiste en gros à se souvenir des modifications apportées au schéma (tables, vues, etc) et éventuellement aux données dans le schéma pendant tout le cycle de vie de l’application. Le but étant clairement de gérer la persistance d’attributs inexistants ou supprimés depuis la dernière synchronisation avec le support de persistance (SGBD-R, CouchDB, etc).

Dans l’écosystème Django, South semble le plus aboutit pour la gestion de ce qui est désormais connu sous le vocable de Migration. Parmi les éléments qui font la force de South on peut citer le fait qu’il génère un script de migration en Python très propre et très lisible avec une API Base de données et un ORM par lequel on a accès aux modèles (South a un grand nombre de vote de la communauté pour une intégration à django/contrib éventuelle et nombre de développeurs Django soumettent régulièrement des patchs à South:

from south.db import db
from django.db import models
from core.models import *

class Migration:
    
    def forwards(self, orm):
        "Write your forwards migration here"
    
    def backwards(self, orm):
        "Write your backwards migration here"
    
    models = {
        'monappli.monmodele': {
            'unedate': ('models.DateField', ["'Date de Heu'"], {}),
        },
        'mon.monautremodele': {
            'monmodele': ('models.ForeignKey', 
                         ["orm['monappli.monmodele']"], 
                         {'related_name': "'boubougolo'"}
                         ),
        },
    }
    
    complete_apps = ['monappli']

Les méthodes `forwards()` et `backwards()` peut être utilisées pour toute les manipulations de données souhaitées et cela avec l’aide précieuse l’API db. Un très bon exemple est donné dans le tutoriel, il consiste à remplacer un champ nom_complet avec les champs prenoms et noms en copiant les données déjà définit dans le nom_complet:

# Exemple du tutoriel3: http://south.aeracode.org/wiki/Tutorial3
 def forwards(self, orm):
        db.add_column('monappli_monmodele', 
                      'prenoms', 
                      models.CharField(max_length=150)
                     )
        db.add_column('monappli_monmodele', 
                      'noms', 
                       models.CharField(max_length=150)
                     )

        for item in orm.MonModele.objects.all():
            pass # ... Une copie de données par exemple ...
        
        db.delete_column('monappli_monmodele', 'nom_complet')

Il est courant de ne penser à la migration que lorsqu’on a commencé les premiers tests d’une applications après avoir introduit quelques données de test. South permet simplement de mettre en œuvre une migration après coup:

Avec la version >= 0.5:

./manage.py startmigration monappli --initial
./manage.py migrate monappli

Avec les versions antérieures:

./manage.py convert_to_south monappli

South support l’auto-détection de modification lorsque l’application est gélées (dans settings.py):

SOUTH_AUTO_FREEZE_APP = True

En auto-détection il suffit simplement après des modifications dans les modèles de demander l’auto-détection:

python manage.py startmigration monappli nom_migration --auto

Et si les modifications détectées sont correctes alors il suffit de lancer la migration:

python manage.py migrate monappli

Celle-ci se traduira par une série de directives de type DDL sur le support de stockage de façon agnostique aux support de stockage comme l’ORM de Django lui-même

Pour couronner le tout, South-0.5.2 est disponible dans sid sous le nom python-django-south

South est relativement bien documenté, vous pouvez suivre le tutoriel qui donne tous les éléments pour être à l’aise avec South. Vous pouvez aussi lire l’article de Andrew Godwin sur les choix d’architecture de South.

Erlang aussi dans le web framework

écrit par wilane, le 14 mai 2009 12:19:00.

Avec Nitrogen Erlang est en plein dans la danse. Avec Seaside (Smalltalk), Django (Python) et ses frères, Weblocks (Common Lisp), HApps (Haskell) et Rails (Ruby), c’est une vraie alternative sérieuse qui apporte la force d’Erlang sur la table (remise à l’échelle innée).

Pour le côté bling bling Nitrogen fait un choix qui devient standard de fait: jQuery

Ecrire un greffon Zine par l'exemple

écrit par wilane, le 13 mai 2009 22:06:00.

Ce greffon flickr permettra d’afficher les albums (sets) de photos

Pour ce projet on utilisera une api flickr appelée flickrapi redoutable par sa simplicité.

Arborescence du projet

  • flickr/
    • docs/
    • i18n/
    • __init__.py
    • metadata.txt
    • templates/
      • admin/

Notez que dans le répertoire `scripts` du code source vous trouverez un script `new-plugin` permettant de définir l’échafaudage ci-dessus avec les méta données sur l’auteur et le greffon consignées dans `metadata.txt’.

Interface admin

Il faut permettre au Blogeur d’enregistrer son API KEY et le USER_ID pour lequel il va afficher les albums de photo, pour cela on ouvre __init__.py avec emacs (si vous utilisez un autre éditeur arrêter de lire tout de suite):

from os.path import dirname, join

from werkzeug import escape, url_encode

import zine
from zine.api import *
from zine.widgets import Widget
from zine.views.admin import flash, render_admin_response
from zine.privileges import BLOG_ADMIN, require_privilege
from zine.utils.validators import ValidationError, check
from zine.utils.http import redirect_to
from zine.utils import forms

USER_AGENT = 'Zine /%s | Flickr/0.1' % zine.__version__
FLICKR_URL_BASE = 'static.flickr.com'
FLICKR_VERSION = '0.1'
TEMPLATES = join(dirname(__file__), 'templates')

def is_valid_key():
    def validate(form, apikey):
        pass
    return validate

def is_valid_userid():
    def validate(form, userid):
        pass
    return validate

def add_flickr_link(req, navigation_bar):
    if req.user.has_privilege(BLOG_ADMIN):
        for link_id, url, title, children in navigation_bar:
            if link_id == 'options':
                children.insert(-3, ('flickr',
                                     url_for('flickr/config'),
                                     _('Flickr')))

class ConfigurationForm(forms.Form):
    api_key = forms.TextField(validators=[is_valid_key()])
    user_id = forms.TextField(validators=[is_valid_userid()])

@require_privilege(BLOG_ADMIN)
def show_flickr_config(req):
    form = ConfigurationForm(initial=dict(
        api_key=req.app.cfg['flickr/apikey'],
        user_id=req.app.cfg['flickr/userid'],
    ))

    if req.method == 'POST' and form.validate(req.form):
        if form.has_changed:
            req.app.cfg.change_single('flickr/apikey',
                                      form['api_key'])
            req.app.cfg.change_single('flickr/userid',
                                      form['user_id'])
            if form['api_key'] and form['user_id']:
                flash(_('Flickr has been successfully enabled.'), 'ok')
            else:
                flash(_('Flickr disabled.'), 'ok')
        return redirect_to('flickr/config')
    return render_admin_response('admin/flickr.html',
                                 'options.flickr',
                                 form=form.as_widget())


def setup(app, plugin):
    app.add_config_var('flickr/apikey',
                       forms.TextField(default=u''))

    app.add_config_var('flickr/userid',
                       forms.TextField(default=u''))

    app.add_url_rule('/options/flickr', prefix='admin',
                     endpoint='flickr/config',
                     view=show_flickr_config)

    app.connect_event('modify-admin-navigation-bar', add_flickr_link)

Avec ce petit bout de code vous réalisez l’essentiel du travail de configuration du greffon. `add_config_var()’ définit les deux variables de configuration qui seront enregistrées dans le fichier `zine.ini’ dans le répertoire de l’instance, `add_url_rule()’ définit le mapping entre l’url et la vue qui traite l’url et enfin `add_flickr_link()’ est invoquée lorsque la barre de navigation de l’admin est déssinée. Le reste du code se lis simplement.

Template admin/flickr.html

{% extends "admin/layout.html" %}
{% block title %}{{ _("Flickr Configuration") }}{% endblock %}
{% block contents %}
  <h1>{{ _("Flickr Configuration") }}</h1>
  <p>Flickr integration. If you don't have a flickr.com account yet, you can
    get one at <a href="{{ api_link }}">flickr.com</a>.
  </p>

  {%- call form() %}
  <h2>API Key</h2>
    <p>{{ form.api_key() }}</p>
  <h2>User Id</h2>
    <p>{{ form.user_id() }}</p>
    <div class="actions">
      <input type="submit" value="{{ _('Update') }}">
    </div>
  {%- endcall %}
{% endblock %}

Widget flickr du Blog

Avec une API KEY et un USER ID, nous allons définir la fonction permettant de récupérer les albums de chez flickr, ensuite de faire le rendu du widget qui va afficher les vignettes avec un lien vers la page de l’album sur flickr.

def jsonFlickrApi(q):
    return q

def get_sets_urls():
    # FIXME: Error checking please
    import flickrapi
    app = get_application()
    
    api_key=app.cfg['flickr/apikey']
    user_id=app.cfg['flickr/userid']    
    flickr = flickrapi.FlickrAPI(api_key, format='json')
    sets = eval(flickr.photosets_getList(user_id=user_id))
    
    return [{'url':'http://www.flickr.com/photos/wilane/sets/%s' %(i['id'],), 
             'urlimg':'http://farm%s.static.flickr.com/%s/%s_%s_s.jpg' %(i['farm'], i['server'], i['primary'], i['secret'])}  
             for i in sets['photosets']['photoset']]

class FlickrWidget(Widget):
    name = 'get_flickr_sets'
    template = 'flickr_widget.html'

    def __init__(self, show_title=True, title='Flickr sets'):
        self.show_title = show_title
        self.title = title
        self.sets = get_sets_urls() 

    @staticmethod
    def get_display_name():
        return _('Flickr sets')

def setup(app, plugin):
    app.add_widget(FlickrWidget)
    app.add_template_searchpath(TEMPLATES)

Là encore le travail consiste à définir le widget et à lui injecter (`self.sets’) la liste de dictionnaire contenant l’image de l’album (en petite taille s – voire la documentation de l’api sur flickr.com –) et l’url

Template flickr_widget.html

Et enfin voici la template utilisée pour le rendu du widget:

{% extends 'widgets/base.html' %}
{% block title %}{{ widget.title }}{% endblock %}
{% block body %}
 {%- for set in widget.sets %}
  <a href="{{ set.url }}"><img src="{{ set.urlimg }}" /> </a>
 {%- endfor %}
{% endblock %}

Si vous désirez ensuite distribuer votre greffon vous pouvez utiliser le script `bundle-plugin’ dans le répertoire `scripts’ pour produire le `.plugin’ utilisée pour installer votre greffon par l’interface web d’admin d’une instance de Zine

Python African Tour (Etape Sénégal)

écrit par wilane, le 8 mai 2009 22:32:00.

Python African Tour Sénégal

Nous avons l’honneur d’accueillir la Python African Tour à Dakar du 06 au 10 juillet 2009, si vous voulez aider, participer, ne rien faire, visitez le site de l’évenement