# Initialisation et modèles de données de la refonte

Suite de la refonte de ce blog, qui a pris un peu de retard. Aujourd'hui on va de l'initialisation de l'environnement de développement au modèle de données retenu. Je pars vraiment de la base pour que vous puissiez réellement suivre la refonte si vous êtes intéressé(e). C'est d'ailleurs la raison pour laquelle j'ai mis le code sur un dépôt public (cf. fin du billet).

## Initialisation

Commençons par installer django, j'ai décidé de mettre les sources dans ~/web/ car j'avais l'habitude d'avoir la dernière version à jour et c'est quand même plus pratique d'être dans sa home pour faire un svn up. Depuis la sortie de la 0.96, la version du dépôt officiel n'est plus aussi stable qu'avant (comme il était prévu), nous allons donc installer la version 0.96 pour la refonte :

    $ cd & cd web
    $ wget http://www.djangoproject.com/download/0.96/tarball/
    $ tar xvf Django-0.96.tar.gz && rm Django-0.96.tar.gz
    $ sudo ln -sf /home/david/web/Django-0.96/django /usr/lib/python2.5/site-packages/

À adapter bien entendu à votre configuration (python 2.5 par défaut avec Feisty \o/). Normalement la commande ls -l /usr/lib/python2.5/site-packages/dj* devrait vous renvoyer :

    lrwxrwxrwx 1 root root 34 2007-04-02 08:32 /usr/lib/python2.5/site-packages/django -> /home/david/web/Django-0.96/django

Vous pouvez aussi vérifier en faisant un python -c "import django;print django.VERSION" qui ne devrait pas lever d'erreur et renvoyer (0, 96, None). Si vous récupérez directement les sources du projet, les étapes d'initialisation suivantes sont inutiles.

Ok, tout marche bien, créons donc notre projet de refonte (par commodité, je me mets dans le dossier refonte) :

    $ mkdir refonte && cd refonte
    $ /usr/lib/python2.5/site-packages/django/bin/django-admin.py startproject biologeek
    $ cd biologeek/

Vous pouvez aussi mettre le répertoire bin de django dans votre $PATH pour ne pas avoir à aller le récupérer dans les paquets python mais comme je ne m'en sers que pour la création de projets je trouve ça un peu inutile.

Il ne reste plus qu'à paramétrer tout ça en modifiant le fichier settings.py et vous pourrez alors synchroniser la base pour la première fois :

    $ python manage.py syncdb

La création d'un super-utilisateur est demandée, une fois cet utilisateur créé, vous pouvez lancer votre serveur de développement pour la première fois :

    $ python manage.py runserver

En vous rendant sur http://127.0.0.1:8000/ vous aurez le message de bienvenue et sur /admin/ vous pourrez accéder à votre interface d'administration (en décommentant la ligne dans urls.py).

On va maintenant créer les deux « applications » de la refonte : journal et bistrot.

    $ python manage.py startapp journal
    $ python manage.py startapp bistrot

Je m'arrête là pour l'initiation à Django, ces bases là suffisent pour comprendre ce qui va suivre, si vous voulez en savoir plus rien ne vaut un bon tutoriel.

## Modèles de données

Le modèle de données doit être placé dans les fichiers models.py situés dans les répertoires des deux applications créées. Le journal va contenir des billets qui auront le modèle suivant :

    class Post(models.Model):
        title = models.CharField('Titre', maxlength=200)
        slug = models.SlugField('Adresse', prepopulate_from=('title',), primary_key='True')
        tags = models.ManyToManyField(Tag, help_text='Sélectionnez les tags.')

        summary = models.TextField('Description', help_text='Différentes syntaxes possibles (cf. markup)', blank=True)
        summary_html = models.TextField('Description HTML', blank=True)
        content = models.TextField('Contenu', help_text='Difféntes syntaxes possibles (cf. markup)')
        content_html = models.TextField('Contenu HTML', blank=True)

        related_posts = models.ManyToManyField('self', help_text='Sélectionnez les billets en relation.', blank=True)
        related = models.TextField('Contenu additionnel', help_text='Difféntes syntaxes possibles (cf. markup)', blank=True)
        related_html = models.TextField('Contenu additionnel HTML', blank=True)

        creation_date = models.DateTimeField('Date de création')
        modification_date = models.DateTimeField('Date de modification')
        publication_date = models.DateTimeField('Date de publication', null=True, blank=True)

        image = models.ImageField('Image associée', upload_to='%s/media/logos/' % settings.PROJECT_PATH, blank=True)
        is_draft = models.BooleanField('Brouillon', default=True)
        is_bestof = models.BooleanField('Best of', default=False)
        markup = models.CharField(maxlength=20, choices=MARKUP_CHOICES, radio_admin=True, default='markdown')

Je ne vais pas détailler, les champs parlent d'eux-même. Par contre, il est intéressant de commenter le manager que j'ai associé :

    class PostManager(models.Manager):
        def get_query_set(self):
            """ Retrieve only published posts. """
            qs = super(PostManager, self).get_query_set()
            return qs.filter(is_draft=False)

        def get_best_of(self):
            """ Retrieve only best of published posts. """
            qs = super(PostManager, self).get_query_set()
            return qs.filter(is_bestof=True)

Un manager permet d'accéder facilement à un catégorie de billets, il faut pour cela ajouter à votre classe Post par exemple :

    class Post(models.Model):
        [...]
        objects = models.Manager()
        published = PostManager()

Et la récupération des données est simplifiée ainsi :

- Post.objects.all() pour l'ensemble des données ;
- Post.published.all() pour l'ensemble des données publiées ;
- Post.published.get_best_of() pour l'ensemble des données publiées et sélectionnées.

On peut faire beaucoup de choses avec les managers et c'est relativement pratique pour ne pas se tromper lorsque l'on doit continuellement filtrer des données à accès restreint.

Pour les brèves du bistrot, le code est assez similaire :

    class Thought(models.Model):
        title = models.CharField('Titre', maxlength=200)
        slug = models.SlugField('Adresse', prepopulate_from=('title',))
        tags = models.ManyToManyField(Tag, help_text='Sélectionnez les tags.')

        content = models.TextField('Contenu', help_text='Vous pouvez utiliser markdown')
        content_html = models.TextField('Contenu HTML', blank=True)

        creation_date = models.DateTimeField('Date de création')
        modification_date = models.DateTimeField('Date de modification')
        publication_date = models.DateTimeField('Date de publication', null=True, blank=True)

Il y a juste le manager dans lequel je suis en train d'intégrer les votes donc j'en parlerais probablement plus tard, je vais m'arrêter là sinon je publierais jamais ce billet :-).

## Accès au code source

J'ai créé pour l'occasion un projet sur Google Code qui contient l'intégralité du code. Vous pouvez facilement récupérer le code avec subversion :

    svn checkout http://biologeek.googlecode.com/svn/trunk/ biologeek

Checkoutez, testez, participez !

### Articles peut-être en rapport

- Biologeek (enfin) propulsé par Django
- Ajout des flux RSS, du sitemap et des commentaires avec Django
- De Dotclear à Django : migration des données et redirections

### Commentaires

NiCoS le 12/04/2007 :

Pour django-admin.py, tu peux aussi faire un simple lien symbolique :
sudo ln -s /usr/lib/python2.5/site-packages/django/bin/django-admin.py /usr/local/bin/django-admin.py
Sinon si tu utilises Eclipse, PyDev ne suit pas les liens symboliques, dont tu dois ajouter /home/david/web/Django-0.96/ à ton PythonPath d'eclipse. Histoire d'avoir l'autocomplétion notamment :-)
A quoi te servent les champ *_html ?
Sinon intéressant le coup des manager, je connaissais pas, tout comme plein de petites choses que tu as mis à droite et à gauche :-)

David, biologeek le 12/04/2007 :

> Pour django-admin.py, tu peux aussi faire un simple lien symbolique
Tout à fait, mais bon comme je disais ça sert pas tous les jours.
> Sinon si tu utilises Eclipse
Ma ram a généralement une meilleure utilité ;-).
> A quoi te servent les champ *_html ?
Je convertis selon le markup le contenu en html et je le stocke en base pour ne pas que la conversion soit faite à la volée à chaque chargement de page dans le template.

NiCoS le 12/04/2007 :

Hmm pour les champ *_html, quel est le gain par rapport au fait d'utiliser le cache de django par ex ?

vincent le 12/04/2007 :

Pour le coup des managers, quand j'en fait un (notament pour iki), je faisais une QuerySet avec à tout les coups, pour pouvoir enchainer les différents critères (vu que c'est des objets QuerySet).
Et j'utilise souvent le même Manager pour objects et `published`, avec un argument pris en compte dans le __init__(). Et les propriétés peuvent être fun aussi avec les managers :P
Sinon, QdB :D Je vais m'en inspiré pour shortbrain (surtout le coup des *_html)

cédric le 18/06/2009 :

Je mets mon commentaire ici parce que c'est le seul qui fait référence au champ ImageField.

J'ai déclaré dans une classe un champ tel que :
picture = ImageField(upload_to='.')

Quand j'utilise l'interface admin
j'arrive à charger mon image et à la sauvegarder
dans le dossier MEDIA_ROOT

Quand je clique sur le lien proposé par l'interface admin, j'ai l'impression que Django ne réutilise pas MEDIA_ROOT mais cherche dans le dossier admin des sources django, pourquoi ?

David, biologeek le 18/06/2009 :

@cédric : tu as pensé à spécifier le setting MEDIA_URL ?

http://docs.djangoproject.com/en/dev/ref/settings/#media-url

cedric le 19/06/2009 :

oui, justement il va chercher dans :
/usr/lib/python2.6/dist-packages/django/contrib/admin/media/P1060799.JPG

il ne tiens pas compte de MEDIA_URL :
MEDIA_URL = 'http://127.0.0.1:8000/media/'

mais de AMIN_MEDIA_PREFIX :
ADMIN_MEDIA_PREFIX = '/media/'

si ADMIN_MEDIA_PREFIX = '/media/toto/'
alors il cherchera à l'adresse : /usr/lib/python2.6/dist-packages/django/contrib/admin/media/toto/P1060799.JPG

rappel : j'essaie déjà via l'interface admin

cédric le 19/06/2009 :

oui, justement il va chercher dans :
/usr/lib/python2.6/dist-packages/django/contrib/admin/media/P1060799.JPG

il ne tiens pas compte de MEDIA_URL :
MEDIA_URL = 'http://127.0.0.1:8000/media/'

mais de AMIN_MEDIA_PREFIX :
ADMIN_MEDIA_PREFIX = '/media/'

si ADMIN_MEDIA_PREFIX = '/media/toto/'
alors il cherchera à l'adresse : /usr/lib/python2.6/dist-packages/django/contrib/admin/media/toto/P1060799.JPG

rappel : j'essaie déjà via l'interface admin

David, biologeek le 20/06/2009 :

Il doit y avoir conflit entre ADMIN_MEDIA_PREFIX et MEDIA_URL.

Je te conseille de tester :
MEDIA_URL = '/media/'
ADMIN_MEDIA_PREFIX = '/admin_media/'

Et si ça marche pas de passer par la liste django francophone, ça sera plus approprié et il y a plus de monde ;)
http://lists.afpy.org/mailman/listinfo/django