J'essaie de trouver un moyen d'implémenter à la fois un QuerySet
personnalisé et un Manager
personnalisé sans casser DRY. Voici ce que j'ai jusqu'à présent:
class MyInquiryManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
class Inquiry(models.Model):
ts = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(InquiryStatus)
assigned_to_user = models.ForeignKey(User, blank=True, null=True)
assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
objects = MyInquiryManager()
Cela fonctionne bien, jusqu'à ce que je fasse quelque chose comme ça:
inquiries = Inquiry.objects.filter(status=some_status)
my_inquiry_count = inquiries.for_user(request.user).count()
Cela casse rapidement tout parce que le QuerySet
n'a pas les mêmes méthodes que le Manager
. J'ai essayé de créer une classe QuerySet
personnalisée et de l'implémenter dans MyInquiryManager
, mais je finis par répliquer toutes mes définitions de méthode.
J'ai également trouvé cet extrait qui fonctionne, mais je dois passer l'argument supplémentaire à for_user
donc il tombe en panne car il dépend fortement de la redéfinition get_query_set
.
Existe-t-il un moyen de le faire sans redéfinir toutes mes méthodes dans les sous-classes QuerySet
et Manager
?
Django a changé! Avant d'utiliser le code dans cette réponse, qui a été écrite en 2009, assurez-vous de consulter le reste des réponses et la documentation Django pour voir s'il existe une solution plus appropriée.
J'ai implémenté ceci en ajoutant le get_active_for_account
Comme méthode d'un QuerySet
personnalisé. Ensuite, pour le faire fonctionner hors du gestionnaire, vous pouvez simplement piéger le __getattr__
Et le renvoyer en conséquence
Pour rendre ce modèle réutilisable, j'ai extrait les bits Manager
dans un gestionnaire de modèle distinct:
custom_queryset/models.py
from Django.db import models
from Django.db.models.query import QuerySet
class CustomQuerySetManager(models.Manager):
"""A re-usable Manager to access a custom QuerySet"""
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
# don't delegate internal methods to the queryset
if attr.startswith('__') and attr.endswith('__'):
raise
return getattr(self.get_query_set(), attr, *args)
def get_query_set(self):
return self.model.QuerySet(self.model, using=self._db)
Une fois que vous avez cela, sur vos modèles, tout ce que vous avez à faire est de définir un QuerySet
en tant que classe interne personnalisée et de définir le gestionnaire sur votre gestionnaire personnalisé:
your_app/models.py
from custom_queryset.models import CustomQuerySetManager
from Django.db.models.query import QuerySet
class Inquiry(models.Model):
objects = CustomQuerySetManager()
class QuerySet(QuerySet):
def active_for_account(self, account, *args, **kwargs):
return self.filter(account=account, deleted=False, *args, **kwargs)
Avec ce modèle, l'un d'eux fonctionnera:
>>> Inquiry.objects.active_for_account(user)
>>> Inquiry.objects.all().active_for_account(user)
>>> Inquiry.objects.filter(first_name='John').active_for_account(user)
UPD si vous l'utilisez avec un utilisateur personnalisé (AbstractUser
), vous devez changer
de
class CustomQuerySetManager(models.Manager):
à
from Django.contrib.auth.models import UserManager
class CustomQuerySetManager(UserManager):
***
La Django 1.7 a publié une nouvelle et simple façon de créer un ensemble de requêtes et un gestionnaire de modèle combinés:
class InquiryQuerySet(models.QuerySet):
def for_user(self):
return self.filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
class Inquiry(models.Model):
objects = InqueryQuerySet.as_manager()
Voir Création de Manager avec les méthodes QuerySet pour plus de détails.
Vous pouvez fournir les méthodes sur le gestionnaire et l'ensemble de requêtes à l'aide d'un mixin. Voir la technique suivante:
http://hunterford.me/Django-custom-model-manager-chaining/
Cela évite également l'utilisation d'une approche __getattr__()
.
from Django.db.models.query import QuerySet
class PostMixin(object):
def by_author(self, user):
return self.filter(user=user)
def published(self):
return self.filter(published__lte=datetime.now())
class PostQuerySet(QuerySet, PostMixin):
pass
class PostManager(models.Manager, PostMixin):
def get_query_set(self):
return PostQuerySet(self.model, using=self._db)
Une version légèrement améliorée de l'approche de T. Stone:
def objects_extra(mixin_class):
class MixinManager(models.Manager, mixin_class):
class MixinQuerySet(QuerySet, mixin_class):
pass
def get_query_set(self):
return self.MixinQuerySet(self.model, using=self._db)
return MixinManager()
Les décorateurs de classe rendent l'utilisation aussi simple que:
class SomeModel(models.Model):
...
@objects_extra
class objects:
def filter_by_something_complex(self, whatever parameters):
return self.extra(...)
...
Mise à jour: prise en charge des classes de base Manager et QuerySet non standard, e. g. @objects_extra (Django.contrib.gis.db.models.GeoManager, Django.contrib.gis.db.models.query.GeoQuerySet):
def objects_extra(Manager=Django.db.models.Manager, QuerySet=Django.db.models.query.QuerySet):
def oe_inner(Mixin, Manager=Django.db.models.Manager, QuerySet=Django.db.models.query.QuerySet):
class MixinManager(Manager, Mixin):
class MixinQuerySet(QuerySet, Mixin):
pass
def get_query_set(self):
return self.MixinQuerySet(self.model, using=self._db)
return MixinManager()
if issubclass(Manager, Django.db.models.Manager):
return lambda Mixin: oe_inner(Mixin, Manager, QuerySet)
else:
return oe_inner(Mixin=Manager)
Vous pouvez maintenant utiliser la méthode from_queryset () sur votre gestionnaire pour modifier son Queryset de base.
Cela vous permet de définir vos méthodes Queryset et vos méthodes de gestion une seule fois
des documents
Pour une utilisation avancée, vous souhaiterez peut-être à la fois un gestionnaire personnalisé et un ensemble de requêtes personnalisé. Vous pouvez le faire en appelant Manager.from_queryset () qui retourne une sous-classe de votre gestionnaire de base avec une copie des méthodes QuerySet personnalisées:
class InqueryQueryset(models.Queryset):
def custom_method(self):
""" available on all default querysets"""
class BaseMyInquiryManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
MyInquiryManager = BaseInquiryManager.from_queryset(InquiryQueryset)
class Inquiry(models.Model):
ts = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(InquiryStatus)
assigned_to_user = models.ForeignKey(User, blank=True, null=True)
assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
objects = MyInquiryManager()