Gestionnaires

class Manager[source]

Un gestionnaire (objet Manager) est l’interface par laquelle les opérations de requêtes de base de données sont mises à disposition des modèles Django. Il existe au moins un Manager pour chaque modèle d’une application Django.

Le fonctionnement des classes Manager est documenté dans Création de requêtes; ce document aborde spécifiquement les options de modèles qui personnalisent le comportement des gestionnaires.

Noms des gestionnaires

Par défaut, Django ajoute un gestionnaire nommé objects à chaque classe de modèle Django. Cependant, si vous aimeriez utiliser objects comme nom de champ ou que vous aimeriez nommer le gestionnaire autrement que objects, vous pouvez le renommer au niveau du modèle. Pour renommer le gestionnaire d’une classe donnée, définissez un attribut de classe de type models.Manager() dans le modèle. Par exemple :

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

En utilisant cet exemple de modèle, Person.objects générera une exception AttributeError, mais Person.people.all() fournira effectivement la liste de tous les objets Person.

Gestionnaires personnalisés

Vous pouvez utiliser un gestionnaire personnalisé dans un modèle particulier en étendant la classe Manager de base et en créant votre propre instance de Manager dans votre modèle.

Il y a deux raisons de vouloir personnaliser un gestionnaire : pour lui ajouter des méthodes supplémentaires ou pour modifier l’objet QuerySet initial que le gestionnaire renvoie.

Ajout de méthodes de gestionnaire supplémentaires

L’ajout de méthodes de gestionnaire supplémentaires est la façon privilégiée d’ajouter des fonctionnalités au « niveau table » à des modèles (pour les fonctionnalités au « niveau ligne », c’est-à-dire les fonctions qui agissent sur une seule instance d’un objet de modèle, utilisez les méthodes de modèles et non pas des méthodes de gestionnaire personnalisées).

Une méthode de gestionnaire personnalisée peut renvoyer tout ce qu’on veut, elle n’est pas tenue de renvoyer un objet QuerySet.

Par exemple, cette méthode de gestionnaire personnalisée offre une méthode with_counts() qui renvoie une liste de tous les objets OpinionPoll, chacun recevant un attribut supplémentaire num_responses qui est le résultat d’une requête d’agrégation :

from django.db import models

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        with connection.cursor() as cursor:
            cursor.execute("""
                SELECT p.id, p.question, p.poll_date, COUNT(*)
                FROM polls_opinionpoll p, polls_response r
                WHERE p.id = r.poll_id
                GROUP BY p.id, p.question, p.poll_date
                ORDER BY p.poll_date DESC""")
            result_list = []
            for row in cursor.fetchall():
                p = self.model(id=row[0], question=row[1], poll_date=row[2])
                p.num_responses = row[3]
                result_list.append(p)
        return result_list

class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    poll_date = models.DateField()
    objects = PollManager()

class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

Avec cet exemple, vous écririez OpinionPoll.objects.with_counts() pour obtenir la liste des objets OpinionPoll comportant l’attribut num_responses.

Une autre chose à relever au sujet de cet exemple est que les méthodes de gestionnaire ont accès à self.model pour obtenir la classe de modèle à laquelle elles sont liées.

Modification des QuerySet initiaux des gestionnaires

Le QuerySet de base d’un gestionnaire renvoie tous les objets du système. Par exemple, en utilisant ce modèle :

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

…l’instruction Book.objects.all() renvoie tous les livres de la base de données.

Vous pouvez surcharger le QuerySet de base d’un gestionnaire en surchargeant la méthode Manager.get_queryset(). get_queryset() doit renvoyer un objet QuerySet doté des propriétés nécessaires.

Par exemple, le modèle suivant possède deux gestionnaires, un qui renvoie tous les objets et un autre qui ne renvoie que les livres de Roald Dahl :

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    dahl_objects = DahlBookManager() # The Dahl-specific manager.

Avec cet exemple de modèle, Book.objects.all() renvoie tous les livres de la base de données, mais Book.dahl_objects.all() ne retourne que ceux qui ont été écrits par Roald Dahl.

Naturellement, comme get_queryset() renvoie un objet QuerySet, vous pouvez appliquer filter(), exclude() et toutes les autres méthodes de QuerySet. Ainsi, ces instructions sont toutes valables :

Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()

Cet exemple a aussi mis en évidence une autre technique intéressante : l’emploi de plusieurs gestionnaires dans un même modèle. Vous pouvez lier autant d’instances de gestionnaires que vous voulez à un modèle. C’est une manière simple de définir des « filtres » fréquemment utilisés dans des modèles.

Par exemple :

class AuthorManager(models.Manager):
    def get_queryset(self):
        return super(AuthorManager, self).get_queryset().filter(role='A')

class EditorManager(models.Manager):
    def get_queryset(self):
        return super(EditorManager, self).get_queryset().filter(role='E')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
    people = models.Manager()
    authors = AuthorManager()
    editors = EditorManager()

Cet exemple permet d’effectuer les requêtes Person.authors.all(), Person.editors.all() et Person.people.all() en sachant à quoi vous attendre au niveau des résultats.

Gestionnaires par défaut

Model._default_manager

Si vous utilisez des objets Manager personnalisés, sachez que le premier Manager que Django rencontre (dans l’ordre où ils ont été définis dans le modèle) reçoit un statut spécial. Django interprète le premier gestionnaire défini dans une classe comme le gestionnaire par défaut, et plusieurs parties de Django (y compris dumpdata) utilisent exclusivement ce gestionnaire pour le modèle en question. En conséquence, il est conseillé de choisir avec prudence le gestionnaire par défaut afin d’éviter une situation où la surcharge de get_queryset() aboutit à l’incapacité de récupérer des objets avec lesquels vous avez besoin de travailler.

Vous pouvez définir un gestionnaire par défaut personnalisé en utilisant Meta.default_manager_name.

Si par exemple vous écrivez du code qui doit gérer un modèle inconnu dans une application tierce qui implémente une vue générique, utilisez ce gestionnaire (ou _base_manager) plutôt que de supposer que le modèle possède un gestionnaire objects.

Gestionnaires de base

Model._base_manager

Ne jamais filtrer les résultats dans ce type de sous-classe de gestionnaire

Ce gestionnaire est utilisé pour accéder aux objets liés à partir d’un autre modèle. Dans ces situations, Django doit être capable de voir tous les objets du modèle qu’il récupère, afin que tout ce qui est référencé puisse être récupéré.

Si vous surchargez la méthode get_queryset() et que vous excluez certaines lignes, Django renverra des résultats incorrects. Ne faites pas cela. Un gestionnaire excluant des résultats dans get_queryset() n’est pas approprié comme gestionnaire de base.

Appel personnalisé de méthodes QuerySet depuis le gestionnaire

Alors que la plupart des méthodes d’un QuerySet standard sont directement accessibles à partir d’un Manager, ce n’est le cas pour les méthodes supplémentaires définies sur un QuerySet personnalisé que si vous les implémentez également sur le Manager:

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role='A')

    def editors(self):
        return self.filter(role='E')

class PersonManager(models.Manager):
    def get_queryset(self):
        return PersonQuerySet(self.model, using=self._db)

    def authors(self):
        return self.get_queryset().authors()

    def editors(self):
        return self.get_queryset().editors()

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
    people = PersonManager()

Cet exemple permet d’appeler authors() et editors() directement depuis le gestionnaire Person.people.

Création d’objet Manager avec des méthodes de QuerySet

En lieu et place de l’approche ci-dessus qui nécessite de dupliquer les méthodes à la fois sur les objets QuerySet et Manager, QuerySet.as_manager() peut être utilisé pour créer une instance de Manager avec une copie des méthodes d’un objet QuerySet personnalisé :

class Person(models.Model):
    ...
    people = PersonQuerySet.as_manager()

L’instance de Manager créée par QuerySet.as_manager() sera pratiquement identique au PersonManager de l’exemple précédent.

Toutes les méthodes QuerySet n’ont pas forcément de sens au niveau Manager; par exemple, nous empêchons volontairement la méthode QuerySet.delete() d’être copiée vers la classe Manager.

Les méthodes sont copiées selon les règles suivantes :

  • Les méthodes publiques sont copiées par défaut.
  • Les méthodes privées (commençant par un soulignement) ne sont pas copiées par défaut.
  • Les méthodes avec un attribut queryset_only à False sont toujours copiées.
  • Les méthodes avec un attribut queryset_only à True ne sont jamais copiées.

Par exemple :

class CustomQuerySet(models.QuerySet):
    # Available on both Manager and QuerySet.
    def public_method(self):
        return

    # Available only on QuerySet.
    def _private_method(self):
        return

    # Available only on QuerySet.
    def opted_out_public_method(self):
        return
    opted_out_public_method.queryset_only = True

    # Available on both Manager and QuerySet.
    def _opted_in_private_method(self):
        return
    _opted_in_private_method.queryset_only = False

from_queryset()

classmethod from_queryset(queryset_class)

Pour une utilisation plus avancée, il peut être souhaitable d’avoir à la fois un Manager personnalisé et un QuerySet personnalisé. Vous pouvez le faire en appelant Manager.from_queryset() qui renvoie une sous-classe du Manager de base avec une copie des méthodes QuerySet personnalisées :

class BaseManager(models.Manager):
    def manager_only_method(self):
        return

class CustomQuerySet(models.QuerySet):
    def manager_and_queryset_method(self):
        return

class MyModel(models.Model):
    objects = BaseManager.from_queryset(CustomQuerySet)()

Vous pouvez également stocker la classe générée dans une variable :

CustomManager = BaseManager.from_queryset(CustomQuerySet)

class MyModel(models.Model):
    objects = CustomManager()

Gestionnaires personnalisés et héritage de modèle

Voici comment Django gère les gestionnaires personnalisés dans le contexte d’héritage des modèles:

  1. Les gestionnaires des classes de base sont toujours hérités par les classes enfants, par le moyen Python habituel de l’ordre de résolution de nom (les noms des classes enfants surchargent ceux de leurs parents).
  2. Si aucun gestionnaire n’est déclaré dans un modèle ou dans ses parents, Django crée automatiquement le gestionnaire objects.
  3. Le gestionnaire par défaut d’une classe est soit celui qui est choisi par Meta.default_manager_name, soit le premier gestionnaire déclaré dans le modèle, soit le gestionnaire par défaut du premier modèle parent.
Changed in Django 1.10:

Certains comportements d’héritage décrits ci-dessus ne s’appliquent pas sans l’attribut manager_inheritance_from_future = True sur la classe Meta des modèles. Dans les anciennes versions et quand cet attribut est absent, l’héritage des gestionnaires varie en fonction du type d’héritage de modèle (Classes de base abstraites, Héritage multi-table ou Modèles mandataires), tout particulièrement en ce qui concerne le choix du gestionnaire par défaut.

Ces règles assurent la souplesse nécessaire permettant d’installer un ensemble de gestionnaires personnalisés sur un groupe de modèles via une classe de base abstraite, tout en personnalisant aussi le gestionnaire par défaut. Prenons l’exemple de cette classe de base :

class AbstractBase(models.Model):
    # ...
    objects = CustomManager()

    class Meta:
        abstract = True

Si vous l’utilisez directement dans une sous-classe, objects sera le gestionnaire par défaut si aucun gestionnaire n’est déclaré dans la classe de base :

class ChildA(AbstractBase):
    # ...
    # This class has CustomManager as the default manager.
    pass

Si vous voulez hériter de AbstractBase, mais avec un autre gestionnaire par défaut, vous pouvez définir le gestionnaire par défaut dans la classe enfant :

class ChildB(AbstractBase):
    # ...
    # An explicit default manager.
    default_manager = OtherManager()

Ici, default_manager est le gestionnaire par défaut. Le gestionnaire objects est toujours disponible, puisqu’il est hérité. Il n’est juste plus le gestionnaire par défaut.

Pour terminer avec cet exemple, supposons que vous vouliez ajouter des gestionnaires supplémentaires dans la classe enfant tout en conservant le gestionnaire par défaut de AbstractBase. Vous ne pouvez pas ajouter directement le nouveau gestionnaire dans la classe enfant, car cela surchargerait le gestionnaire par défaut et vous devriez aussi inclure explicitement tous les gestionnaires de la classe de base abstraite. La solution est de placer les gestionnaires supplémentaires dans une autre classe de base et introduire celle-ci dans la hiérarchie d’héritage après ceux par défaut :

class ExtraManager(models.Model):
    extra_manager = OtherManager()

    class Meta:
        abstract = True

class ChildC(AbstractBase, ExtraManager):
    # ...
    # Default manager is CustomManager, but OtherManager is
    # also available via the "extra_manager" attribute.
    pass

Notez que même si vous pouvez définir un gestionnaire personnalisé dans le modèle abstrait, vous ne pouvez appeler aucune de ses méthodes au travers du modèle abstrait. C’est-à-dire :

ClassA.objects.do_something()

est légal, mais :

AbstractBase.objects.do_something()

générera une exception. C’est parce que les gestionnaires sont censés intégrer la logique de gestion d’ensembles d’objets. Comme il n’est pas possible d’avoir un ensemble d’objets abstraits, leur gestion n’a pas de sens. Si vous gérez des fonctionnalités s’appliquant au modèle abstrait, vous devriez les placer dans une méthode staticmethod ou classmethod du modèle abstrait.

Détails d’implémentation

Quelles que soient les fonctionnalités ajoutées à un gestionnaire personnalisé, il doit toujours être possible de faire une copie légère (« shallow ») d’une de ses instances ; c’est-à-dire que le code suivant doit fonctionner :

>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)

Django effectue des copies légères des objets de gestionnaire durant certaines requêtes ; si votre gestionnaire ne peut pas être copié, ces requêtes échoueront.

Ce ne sera pas un problème pour la plupart des gestionnaires personnalisés. Si vous ne faites qu’ajouter des méthodes simples à votre classe Manager, il est improbable que vous empêchiez la copie de votre gestionnaire sans le vouloir. Cependant, si vous surchargez __getattr__ ou d’autres méthodes privées de l’objet Manager qui contrôlent l’état de l’objet, il faut vous assurer que vous n’altériez pas la capacité de votre gestionnaire d’être copié.

Back to Top