• en
  • Language: fr

Gestionnaires

class Manager

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
        cursor = connection.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)
    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

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.

Appel personnalisé de méthodes QuerySet depuis le Manager

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

New in Django 1.7.

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

L’héritage de classes et les gestionnaires de modèle ne font pas très bon ménage. Les gestionnaires sont souvent spécifiques aux classes dans lesquelles ils sont définis et leur héritage dans des sous-classes n’est pas forcément toujours une bonne idée. Et comme le premier gestionnaire défini devient le gestionnaire par défaut, il est important de pouvoir contrôler cet ordre de définition. Voici donc comment Django se comporte face aux gestionnaires personnalisés et à l’héritage de modèles:

  1. Les gestionnaires définis dans les classes de base non abstraites ne sont pas hérités par les classes enfants. Si vous souhaitez réutiliser un gestionnaire d’une base non abstraite, il faut le redéclarer explicitement dans la classe enfant. Ce type de gestionnaire est généralement très spécifique à la classe dans laquelle il est défini, c’est pourquoi leur héritage pourrait produire des résultats inattendus (particulièrement lorsqu’il s’agit du gestionnaire par défaut). Ils ne sont donc pas transmis à leurs classes enfants.

  2. Les gestionnaires des classes de base abstraites 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). Les classes de base abstraites sont conçues pour centraliser les informations et les comportements communs à leurs classes enfants. La définition de gestionnaires communs est un aspect entrant tout à fait dans ces informations communes.

  3. Le gestionnaire par défaut d’une classe est soit le premier gestionnaire déclaré dans la classe, s’il y en a un, ou le gestionnaire par défaut de la première classe de base abstraite dans la hiérarchie d’héritage, le cas échéant. Si aucun gestionnaire par défaut n’est explicitement déclaré, c’est le gestionnaire par défaut habituel de Django qui est utilisé.

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é.

Contrôle du type des gestionnaires automatiques

Ce document a déjà mentionné quelques endroits où Django crée automatiquement une classe de gestionnaire : gestionnaires par défaut et le gestionnaire « de base » utilisé pour accéder aux objets liés. Il y a d’autres endroits dans l’implémentation de Django où des gestionnaires de base temporaires sont nécessaires. Ces gestionnaires automatiquement créés sont normalement des instances de la classe django.db.models.Manager.

Tout au long de cette section, nous utiliserons le terme « gestionnaire automatique » pour désigner les gestionnaires que Django crée pour vous, soit comme un gestionnaire par défaut d’un modèle sans gestionnaire ou pour un usage temporaire lors de l’accès à des objets liés.

Parfois, cette classe par défaut n’est pas le meilleur choix. Un exemple se trouve dans l’application django.contrib.gis livrée avec Django. Tous les modèles gis doivent utiliser une classe de gestionnaire spéciale (GeoManager) car ils ont besoin d’un QuerySet spécial (GeoQuerySet) pour interagir avec la base de données. Il en ressort que les modèles ayant besoin d’un gestionnaire spécial comme l’exemple précédent doivent utiliser la même classe de gestionnaire chaque fois qu’un gestionnaire automatique doit être créé.

Django propose aux développeurs de gestionnaires personnalisés une manière d’indiquer que leur classe de gestionnaire doit être utilisée pour les gestionnaires automatiques chaque fois qu’ils deviennent le gestionnaire par défaut d’un modèle. Ceci se fait en définissant l’attribut use_for_related_fields dans la classe du gestionnaire :

class MyManager(models.Manager):
    use_for_related_fields = True
    # ...

Si cet attribut est défini dans le gestionnaire par défaut d’un modèle (seul le gestionnaire par défaut est considéré dans ces situations), Django utilise cette classe chaque fois qu’il a besoin de créer automatiquement un gestionnaire pour la classe. Sinon, il utilise django.db.models.Manager.

Note historique

Étant donné le but pour lequel il est utilisé, le nom de cet attribut (use_for_related_fields) peut sembler un peu bizarre. À l’origine, cet attribut ne contrôlait que le type de gestionnaire utilisé pour l’accès aux champs liés, d’où son nom. Ce nom n’a ensuite pas été modifié, même quand il paraissait de plus en plus clair que le concept était plus largement utile. Ceci principalement pour préserver le fonctionnement du code existant dans les versions futures de Django.

Écriture correcte de gestionnaires pour les instances automatiques

Comme suggéré précédemment dans l’exemple django.contrib.gis, la fonctionnalité use_for_related_fields est principalement axée sur les gestionnaires qui ont besoin de renvoyer une sous-classe de QuerySet personnalisée. Lorsque vous utilisez cette fonctionnalité dans un gestionnaire, il ne fait pas oublier certaines choses.

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

L’une des raisons pour lesquelles un gestionnaire automatique est utilisé est le besoin d’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 automatique.

Back to Top