Question

Je me demandais s'il était possible (et, le cas échéant, comment) d'enchaîner plusieurs gestionnaires pour produire un ensemble de requêtes affecté par les deux gestionnaires individuels. Je vais expliquer l'exemple spécifique sur lequel je travaille:

J'ai plusieurs classes de modèles abstraits que j'utilise pour fournir de petites fonctionnalités spécifiques aux autres modèles. Deux de ces modèles sont un DeleteMixin et un GlobalMixin.

Le DeleteMixin est défini comme tel:

class DeleteMixin(models.Model):
    deleted = models.BooleanField(default=False)
    objects = DeleteManager()

    class Meta:
        abstract = True

    def delete(self):
        self.deleted = True
        self.save()

Fondamentalement, il fournit une pseudo-suppression (l'indicateur supprimé) au lieu de supprimer réellement l'objet.

Le GlobalMixin est défini comme tel:

class GlobalMixin(models.Model):
    is_global = models.BooleanField(default=True)

    objects = GlobalManager()

    class Meta:
        abstract = True

Il permet de définir n'importe quel objet en tant qu'objet global ou privé (tel qu'un message de blog public / privé).

Ces deux organisations ont leurs propres gestionnaires qui affectent le jeu de requêtes renvoyé. Mon DeleteManager filtre le jeu de requêtes pour ne renvoyer que les résultats pour lesquels l'indicateur supprimé est défini sur False, tandis que GlobalManager filtre le jeu de requêtes pour ne renvoyer que les résultats marqués comme globaux. Voici la déclaration pour les deux:

class DeleteManager(models.Manager):
    def get_query_set(self):
        return super(DeleteManager, self).get_query_set().filter(deleted=False)

class GlobalManager(models.Manager):
    def globals(self):
        return self.get_query_set().filter(is_global=1)

La fonctionnalité désirée consisterait à faire en sorte qu'un modèle étende ces deux modèles abstraits et permette de ne renvoyer que les résultats non supprimés et globaux. J'ai exécuté un scénario de test sur un modèle avec 4 instances: une globale et non supprimée, une globale et supprimée, une non globale et non supprimée et une non globale et supprimée. Si j'essaie d'obtenir des ensembles de résultats comme tels: SomeModel.objects.all (), j'obtiens les instances 1 et 3 (les deux non supprimées - génial!). Si j'essaie SomeModel.objects.globals (), j'obtiens une erreur indiquant que DeleteManager n'a pas de valeur globale (cela suppose que ma déclaration de modèle est la suivante: SomeModel (DeleteMixin, GlobalMixin). Si j'inverse l'ordre, pas l’erreur, mais elle ne filtre pas celles qui ont été supprimées). Si je change GlobalMixin pour attacher GlobalManager à des objets globaux au lieu d'objets (la nouvelle commande serait donc SomeModel.globals.globals ()), j'obtiens les instances 1 et 2 (les deux objets globaux), alors que mon résultat souhaité serait de n'obtenir que l'instance 1 (global, non supprimé).

Je n’étais pas sûr que quelqu'un se soit trouvé dans une situation semblable à celle-ci et en soit arrivé à un résultat. Soit un moyen de le faire fonctionner dans ma pensée actuelle ou un remaniement qui fournit les fonctionnalités que je suis après serait très apprécié. Je sais que ce post a été un peu long. Si des explications supplémentaires sont nécessaires, je serais heureux de les fournir.

Modifier:

J'ai posté la solution que j'ai utilisée pour résoudre ce problème spécifique ci-dessous. Il est basé sur le lien vers le QuerySetManager personnalisé de Simon.

Était-ce utile?

La solution

Voir cet extrait sur Djangosnippets: http://djangosnippets.org/snippets/734/

Au lieu de placer vos méthodes personnalisées dans un gestionnaire, vous sous-classez le jeu de requêtes lui-même. C'est très facile et fonctionne parfaitement. Le seul problème que j'ai rencontré concerne l'héritage de modèle: vous devez toujours définir le gestionnaire dans les sous-classes de modèle (juste: "objects = QuerySetManager ()" dans la sous-classe), même s'ils hériteront du groupe de requêtes. Cela sera plus logique une fois que vous utiliserez QuerySetManager.

Autres conseils

Voici la solution spécifique à mon problème en utilisant le QuerySetManager personnalisé de Simon auquel Scott a lié.

from django.db import models
from django.contrib import admin
from django.db.models.query import QuerySet
from django.core.exceptions import FieldError

class MixinManager(models.Manager):    
    def get_query_set(self):
        try:
            return self.model.MixinQuerySet(self.model).filter(deleted=False)
        except FieldError:
            return self.model.MixinQuerySet(self.model)

class BaseMixin(models.Model):
    admin = models.Manager()
    objects = MixinManager()

    class MixinQuerySet(QuerySet):

        def globals(self):
            try:
                return self.filter(is_global=True)
            except FieldError:
                return self.all()

    class Meta:
        abstract = True

class DeleteMixin(BaseMixin):
    deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True

    def delete(self):
        self.deleted = True
        self.save()

class GlobalMixin(BaseMixin):
    is_global = models.BooleanField(default=True)

    class Meta:
        abstract = True

À l’avenir, tout mixeur qui souhaite ajouter des fonctionnalités supplémentaires au jeu de requêtes doit simplement étendre BaseMixin (ou l’avoir quelque part dans sa hiérarchie). À chaque fois que j'essaie de filtrer la requête, je l'enveloppe dans un try-catch au cas où ce champ n'existe pas (c'est-à-dire qu'il ne prolonge pas ce mélange). Le filtre global est appelé à l'aide de globals (), tandis que le filtre de suppression est automatiquement appelé (si quelque chose est supprimé, je ne veux jamais qu'il soit affiché). L'utilisation de ce système permet les types de commandes suivants:

TemporaryModel.objects.all() # If extending DeleteMixin, no deleted instances are returned
TemporaryModel.objects.all().globals() # Filter out the private instances (non-global)
TemporaryModel.objects.filter(...) # Ditto about excluding deleteds

Une chose à noter est que le filtre de suppression n’affectera pas les interfaces d’administration, car le gestionnaire par défaut est déclaré en premier (par défaut). Je ne me souviens pas du moment où ils ont changé l'administrateur pour qu'il utilise Model._default_manager au lieu de Model.objects, mais toute instance supprimée apparaîtra toujours dans l'administrateur (au cas où vous auriez besoin de les supprimer).

J'ai passé un certain temps à essayer de trouver un moyen de construire une belle usine pour le faire, mais cela me pose beaucoup de problèmes.

Le mieux que je puisse vous suggérer est d'enchaîner votre héritage. Ce n'est pas très générique, donc je ne suis pas sûr de son utilité, mais tout ce que vous avez à faire est de:

class GlobalMixin(DeleteMixin):
    is_global = models.BooleanField(default=True)

    objects = GlobalManager()

    class Meta:
        abstract = True

class GlobalManager(DeleteManager):
    def globals(self):
        return self.get_query_set().filter(is_global=1)

Si vous voulez quelque chose de plus générique, le mieux que je puisse trouver est de définir un Mixin et un Manager de base qui redéfinissent get_query_set () (J'imagine que vous ne voulez faire cela qu'une fois; sinon, les choses se compliquent assez), puis transmettez une liste de champs que vous souhaitez ajouter via Mixin .

Cela ressemblerait à ceci (pas du tout testé):

class DeleteMixin(models.Model):
    deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True

def create_mixin(base_mixin, **kwargs):
    class wrapper(base_mixin):
        class Meta:
            abstract = True
    for k in kwargs.keys():
        setattr(wrapper, k, kwargs[k])
    return wrapper

class DeleteManager(models.Manager):
    def get_query_set(self):
        return super(DeleteManager, self).get_query_set().filter(deleted=False)

def create_manager(base_manager, **kwargs):
    class wrapper(base_manager):
        pass
    for k in kwargs.keys():
        setattr(wrapper, k, kwargs[k])
    return wrapper

D'accord, c'est moche, mais qu'est-ce que ça vous apporte? Pour l’essentiel, c’est la même solution, mais beaucoup plus dynamique, et un peu plus sèche, bien que plus complexe à lire.

Vous créez d'abord votre responsable de manière dynamique:

def globals(inst):
    return inst.get_query_set().filter(is_global=1)

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

Ceci crée un nouveau gestionnaire qui est une sous-classe de DeleteManager et a une méthode appelée globals .

Vous créez ensuite votre modèle de mixin:

GlobalDeleteMixin = create_mixin(DeleteMixin,
                                 is_global=models.BooleanField(default=False),
                                 objects = GlobalDeleteManager())

Comme je l'ai dit, c'est moche. Mais cela signifie que vous n'avez pas à redéfinir globals () . Si vous souhaitez qu'un gestionnaire différent ait globals () , vous devez simplement appeler à nouveau create_manager avec une base différente. Et vous pouvez ajouter autant de nouvelles méthodes que vous le souhaitez. Pareil pour le manager, vous continuez simplement à ajouter de nouvelles fonctions qui renverront différents ensembles de requêtes.

Alors, est-ce vraiment pratique? Peut être pas. Cette réponse est davantage un exercice de (ab) utilisant la flexibilité de Python. Je n'ai pas essayé d'utiliser cela, bien que j'utilise certains des principes sous-jacents des classes à extension dynamique pour faciliter l'accès aux choses.

Faites-moi savoir si quelque chose n'est pas clair et je mettrai à jour la réponse.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top