django - queries - ¿QuerySet y Manager personalizados sin romper DRY?
orm django queries (5)
Estoy tratando de encontrar una forma de implementar tanto un QuerySet
personalizado como un Manager
personalizado sin romper DRY. Esto es lo que tengo hasta ahora:
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()
Esto funciona bien, hasta que haga algo como esto:
inquiries = Inquiry.objects.filter(status=some_status)
my_inquiry_count = inquiries.for_user(request.user).count()
Esto rompe rápidamente todo porque QuerySet
no tiene los mismos métodos que el Manager
. Intenté crear una clase QuerySet
personalizada e implementarla en MyInquiryManager
, pero termino replicando todas las definiciones de mis métodos.
También encontré este fragmento que funciona, pero necesito pasar el argumento adicional a for_user
para que se rompa porque depende en gran medida de la redefinición de get_query_set
.
¿Hay alguna forma de hacerlo sin redefinir todos mis métodos en las QuerySet
y Manager
?
El Django 1.7 lanzó una forma nueva y sencilla de crear un conjunto de consultas combinado y un administrador de modelos:
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()
Consulte Crear administrador con los métodos QuerySet para obtener más detalles.
El siguiente funciona para mi.
def get_active_for_account(self,account,*args,**kwargs):
"""Returns a queryset that is
Not deleted
For the specified account
"""
return self.filter(account = account,deleted=False,*args,**kwargs)
Esto está en el administrador predeterminado; entonces solía hacer algo como:
Model.objects.get_active_for_account(account).filter()
Pero no hay ninguna razón por la que no debería funcionar para un administrador secundario.
Puede proporcionar los métodos en el administrador y el conjunto de consultas utilizando un mixin. Ver la siguiente técnica:
http://hunterford.me/django-custom-model-manager-chaining/
Esto también evita el uso de un __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)
Una versión ligeramente mejorada del enfoque 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()
Los decoradores de clase hacen el uso tan simple como:
class SomeModel(models.Model):
...
@objects_extra
class objects:
def filter_by_something_complex(self, whatever parameters):
return self.extra(...)
...
Actualización: soporte para clases base no estándar y de QuerySet, por ejemplo @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)
Django ha cambiado! Antes de usar el código en esta respuesta, que fue escrita en 2009, asegúrese de revisar el resto de las respuestas y la documentación de Django para ver si hay una solución más adecuada.
La forma en que lo implementé es agregando la get_active_for_account
real como método de un QuerySet
personalizado. Entonces, para que funcione el administrador, simplemente puede atrapar el __getattr__
y devolverlo en consecuencia
Para que este patrón sea reutilizable, extraje los bits de Manager
en un administrador de modelo independiente:
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)
Una vez que tenga eso, en sus modelos todo lo que necesita hacer es definir un QuerySet
como una clase interna personalizada y configurar el administrador en su administrador personalizado:
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)
Con este patrón, cualquiera de estos funcionará:
>>> Inquiry.objects.active_for_account(user)
>>> Inquiry.objects.all().active_for_account(user)
>>> Inquiry.objects.filter(first_name=''John'').active_for_account(user)