ORM Django

Ce que je préfère le plus dans Django c'est son ORM. Je le trouve très puissant et très simple à utiliser. Bien sûr il ne répondra pas à tous vos besoins: passer par des requtes SQL est inévitable mais 99,99% du temps vous avez un outil qui vous assiste vraiment.

C'est quoi un ORM?

ORM est l'acronyme anglais de object relational mapping, donc mapping d'objet relationnel en français. Un ORM est une technique de programmation qui donne l'illusion de travailler avec une base de données orientée objet. Pour résumer vous ne faites plus de requetes SQL mais vous travaillez directement avec vos objets.

Pourquoi travailler avec un ORM?

L'ORM de Django est très bien conçu. D'une part il vous permet de lister tous les objets nécessitant des enregistrements (on parle de modèles) dans un endroit prévu à cet effet. Si vous êtes un nouveau développeur sur un projet, vous comprenez ce projet en quelques secondes en visualisant ces fichiers.

Un autre avantage de travailler avec un ORM, c'est qu'il est une couche d'abstraction qui exploite la base de données ce qui fait que le développeur ne se soucie plus du SGDBR que le projet utilise. Comme nous l'avons vu dans un chapitre précédent concernant les bases de données, utiliser SQLite ou MySQL implique des requètes SQL spécifiques dans certains cas. Avec Django, il n'y a pas de différence, c'est lui qui gère de faire la requète compatible en fonction du SGBDR. La compatibilité des bases de données, c'est devenu son boulot, plus le vôtre.

Un modèle est donc pour faire simple une table de données. On lui donne un nom, des champs typés et un comportement. Travailler avec un ORM permet donc une homogéneïté du code, une structure plus solide, plus simple à maintenir et est optimisé pour les requètes les plus simples.

Créer un modèle

Avant toute chose, assurez-vous d'avoir un gestionnaire de base de données fonctionnel. Je vous invite à visualiser cette page pour installer un SGBDR: Installer une base de données pour python .

En mode développement je vous conseille de travailler avec le SGDBR Sqlite3 d'une part parce que niveau configuration il n'y a rien à faire mais surtout cela rend votre projet plus portable, votre base de données n'est qu'un simple fichier. Mais être un fichier ne signifique pas qu'il n'existe pas de navigateur SGDBR: exemple sur Linux il y a Sqliteman qui fait plutôt bien le job. Il permet via une interface de gérer les tables, les vues et les triggers, il gère les espaces de la base de données et les statistiques d'index et bien sur exécuter des requetes SQL.

Si vous faites le choix de travailler avec MySQL par exemple, pensez à vérifier la configuration dans le fichier settings.py

eboutique/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', 
        'OPTIONS' : { "init_command": "SET foreign_key_checks = 0;" },
        'NAME': 'eboutique',
        'USER': 'root',
        'PASSWORD': 'MOTDEPASSE',
        'HOST': '127.0.0.1',                     
        'PORT': '',
    }
}

Pensez également à vérifier si votre application est implémentée dans votre projet. Dans notre cas, notre application backoffice.

eboutique/settings.py

# Application definition

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'backoffice'
)

Ensuite ouvrons le fichier models.py de notre application backoffice et créons deux modèles:

backoffice/models.py

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    code = models.IntegerField()
    
    def __unicode__(self):
        return "{0} [{1}]".format(self.name, self.code)

class ProductItem(models.Model):
    color   = models.CharField(max_length=100)
    product = models.ForeignKey('Product')
    code    = models.IntegerField()
    
    def __unicode__(self):
        return "{0} {{1}} [{2}]".format(self.product.name, self.color, self.code)

Un modèle est donc une simple classe python qui hérite de la classe models.Model

Les champs sont définis dans la classe, on leur donne un nom et un type.

Lançons maintenant la création de la base de données:

olivier@bigone:~/eboutique$ python manage.py syncdb
Creating tables ...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table backoffice_product
Creating table backoffice_product_item

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'olivier'): 
Email address: 
Password: 
Password (again): 
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

Si vous ouvrez votre explorateur de base de données, vous remarquerez que la table backoffice_product a été créée. Une table porte donc le nom de l'application et du modèle pour ainsi éviter les conflits lors d'un ajout d'applications tierces dans votre projet.

Vous pouvez dès à présent utiliser l'option shell fournie par le manager Django du projet (manager.py) pour consulter et éditer des items de modèles.

Ouvrons notre console et exécutons la commande suivante:

Dans le dossier /home/olivier/eboutique

python manager.py shell

Vous devriez optenir ceci:

olivier@bigone:~/eboutique$ python manage.py shell
Python 2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

Créer un objet d'un modèle

Dans le shell vous pouvez créer un objet comme ceci:

>>> from backoffice.models import *
>>> p = Product()
>>> p.name = "ipod"
>>> p.code = 1234
>>> p.save()

La méthode save enregistre les données dans la base. Tant que vous ne l'avez pas appelée, aucune information n'est enregistrée. On aurait pu tout aussi bien créer une instance de Product en utilisant la syntaxe suivante:

>>> Product(name="XXXX", code=1234).save()

Profitons-en pour créer une déclinaison de notre produit (instance de ProductIem) qu'on associe à notre produit.

>>> i = ProductItem()
>>> i.code = 5555
>>> i.color = "blue"
>>> i.product = p
>>> i.save()

La variable p est l'instance du produit que nous avons crée précédemment. Vous ne pouvez pas directement lui donner comme valeur la primary key (clé primaire), une instance Product est attendu.

Alors pour info il existe une autre syntaxe plus concise qui réalise exactement la même action:

i = ProductItem.objects.create(code="5555", color="blue", product=p)

Ce qui nous fait une belle transition pour parler du manager Objects

Objects, le manager des modèles

Vous pouvez recupérer des entrées de votre base de données (sous forme d'objet) via un manager que tout modèle possède et qui s'appelle objects. Nous l'avons utilisé dans les exemples précédent mais sans savoir ce qu'il est et ce qu'il est capable de faire.

Vous pouvez d'ailleurs le voir dans le shell:

>>> Product.objects
<django.db.models.manager.Manager object at 0x7f3cc716e090>

Ce manager possède un grand nombre de méthode qui vous permettra de filtrer les objets en fonction de votre besoin.

Afficher toutes les entrées d'un modèle

Pour afficher toutes les entrées d'un modèle vous pouvez utiliser la méthode all:

>>> Product.objects.all()
[<Product: ipod [1234]>, <Product: ipad retina [1543]>]

Le manager vous retourne une liste qui contient sous forme d'objet toutes les entrées de la base de données.

Récupérer un objet

Vous pouvez demander au manager de vous retourner un objet en utilisant la méthode get:

>>> Product.objects.get(pk=2)
<Product: ipod [1234]>
>>> Product.objects.get(code=1543)
<Product: ipad retina [1543]>

La méthode filter()

La méthode get ne retourne qu'un seul objet, si vous voulez récupérer tous les objets ayant X conditions vous pouvez utiliser la méthode filter:

>>> Product.objects.filter(code=1234)
[<Product: ipod [1234]>]

Dans ce cas, c'est une liste qui est retournée.

Vous pouvez effectuer des recherches plus complexes comme "Commence par":

>>> Product.objects.filter(name__startswith="i")
[<Product: ipod [1234]>, <Product: ipad retina [1543]>]

Le concept "Contient":

>>> Product.objects.filter(name__icontains="ipad")
[<Product: ipad retina [1543]>]

A noter que la recherche "contain" (au lieu de icontain) peut être aussi utilisé mais elle sera sensible à la casse.


Conseils de lecture:

Documentation officielle: Documentation Django sur les modèles
Documentation officielle: Documentation Django sur les champs des modèles