Les modèles de Django

Olivier Mengué

Django

  • Un framework web pour le développement rapide
  • For perfectionists with deadlines
  • Créé par un journaliste pour la publication d'un journal
  • Python
  • Un framework qui (re)fait tout

Django : Tout en un

  • ORM (models)
  • templates (views)
  • URLs propres
  • cache
  • i18n
  • Interface d'administration, pour gérer les données

Les modèles

Du modèle Python vers la base de données

  • On définit le modèle avec des classes Python
  • Django déduit le schéma de la base (DDL)
  • Django fournit une API Python pour manipuler les objets en évitant SQL

Support primitif pour générer un modèle à partir d'une base existante (rétro-ingénierie pour démarrer un projet).

Modèle : Adhérent

 from django.db import models
 class Adherent(models.Model):
   nom = models.CharField("nom", maxlength=50)
   prenom = models.CharField("prénom", maxlength=50)
   naissance = models.DateField("date de naissance")
   email = models.EmailField("e-mail", null=False)
   adhesion = models.DateField("date d'adhésion",
                               auto_now=True,
                               editable=False)
   echeance = models.DateField("date d'échéance",
                               null=False)

En base

Créer le modèle en base :

 ./manage.py syncdb

Code DDL exécuté pour SQLite3 :

 BEGIN;
 CREATE TABLE "adherent" (
    "id" integer NOT NULL PRIMARY KEY,
    "nom" varchar(50) NOT NULL,
    "prenom" varchar(50) NOT NULL,
    "naissance" date NOT NULL,
    "email" varchar(75) NOT NULL,
    "adhesion" date NOT NULL,
    "echeance" date NOT NULL
 );
 COMMIT;

Interface d'administration

 ./manage.py runserver

Créer les données

Manipuler les données, avec la console Python :

 $ ./manage.py shell
 >>> from si.models import Adherent
 >>> Adherent.objects.all()
 []
 >>> from datetime import date
 >>> om = Adherent(nom='Mengué', prenom='Olivier',
 ...              naissance=date(1974, 1, 1),
 ...              adhesion=date(2000, 9, 5),
 ...              echeance=date(2007, 9, 4))
 >>> om.save()
 >>> Adherent.objects.all()
 [<Adherent: Mengué, Olivier>]

Interface d'administration

Modèle : Adhérent

 from django.db import models
 from datetime import date
 class Adherent(models.Model):
   nom = models.CharField("nom", maxlength=50)
   prenom = models.CharField("prénom", maxlength=50)
   # ...
   def __str__(self):
        return '%s, %s' % (self.nom, self.prenom)
   class Meta:
        db_table = 'adherent'
        verbose_name = 'Adhérent'
        verbose_name_plural = 'Adhérents'
   def cotisation_a_jour(self):
        return self.echeance > date.today()

Requêtage

L'API de requêtage utilise la reflexivité de Python

 >>> Adherent.objects.all()
 [<Adherent: Mengué, Olivier>, <Adherent: Lafarine, Francine>]
 >>> Adherent.objects.order_by('nom')
 [<Adherent: Lafarine, Francine>, <Adherent: Mengué, Olivier> ]
 >>> Adherent.objects.filter(nom='Mengué')
 [<Adherent: Mengué, Olivier>]
 >>> Adherent.objects.filter(nom__contains='ine')
 [<Adherent: Lafarine, Francine>]

Équivalent Perl :

 Adherent->objects->filter('nom__contains' => 'ine');

Requêtage : QuerySet

 qs = Adherent.object.all()
       .filter(naissance__gte=date(1966, 1, 1))
       .filter(echeance__gte=date.today())

SQL :

 SELECT * FROM adherent WHERE naissance >= '1966-01-01'
                          AND echeance >= NOW

L'évaluation paresseuse permet des filtres conditionnels:

 if affichage == 'N':
   qs = qs.order_by('naissance')

Modèle : Animateur

class Animateur(models.Model):
   adherent = models.OneToOneField(Adherent,
                                verbose_name='adhérent')
   date_diplome = models.DateField('date du diplôme',
             auto_now_add=True, null=False, editable=True)
   parrain = models.ForeignKey('self', null=True)

Modèle : Rando

class Rando(models.Model):
   CHOIX_RYTHMES = (
      ('L', 'Lent'),
      ('M', 'Moyen'),
      ('S', 'Soutenu'),
      ('R', 'Rapide'),
   )
   jour = models.DateField()
   titre = models.CharField("titre", maxlength=250,
                            unique_for_date="jour")
   distance = models.IntegerField("distance (km)")
   rythme = models.CharField("rythme", maxlength=1,
                             choices=CHOIX_RYTHMES)
   publication = models.DateField()
   descriptif = models.TextField()
   animateurs = models.ManyToManyField(Animateur,
                       related_name='randos')

Pythonismes

 premier = Adherent.object.all()[0]
 cinq_a_dix = Adherent.object.all()[5:10]
 
 for a in Adherent.object.all():
   print a
 Animateur.parrain = None

Choix de conception

Modèles :

  • Explicite est mieux qu'implicite
    • pas de comportement basé sur le nom du champ

  • Toute la logique métier à un seul endroit
    • structure des données

    • méthodes métier

Choix de conception : API

  • SQL efficace
    • minimiser les accès

    • optimisation des requêtes

    • appels explicites : save(), select_related()

  • Syntaxe riche, expressive mais concise
    • Animateur.object.get(adherent__nom='Kelner').randos.all()

    • pas d'objets externes à importer, mais des méthodes simples directement appliquées aux objets

    • jointures transparentes si besoin

    • tous les objets accèdent à tous les autres

  • SQL accessible malgré tout, si nécessaire

Fin

Olivier Mengué

dolmen@cpan.org

http://o.mengue.free.fr/