Использование пользовательского менеджера для фильтрации по обратным связям
У меня есть набор пользователей и набор заданий, которые подает каждый пользователь.
class User(models.Model):
name = models.CharField()
class Assignment(models.Model):
user = models.ForeignKey(
"User",
related_name="assignments"
)
status = models.CharField()
approved = AssignmentActiveManager()
rejected = AssignmentRejectedManager()
...
Я создал пользовательские менеджеры для определения различных состояний назначений, так как это сложно и должно быть внутренним для модели. Например:
class AssignmentActiveManager()
def get_queryset(self):
return Assignment.objects.filter(status__in=["Approved", "Accepted"])
Теперь я хочу получить всех пользователей с одобренным назначением, используя менеджер Assignment.approved
, потому что я не хочу дублировать код фильтрации.
Я могу сделать
Users.objects.filter(assignments__in=Assignments.approved.all()).all()
Однако этот запрос включает в себя WHERE status IN (SELECT ...)
подзапрос. Он будет менее эффективным, чем запрос, созданный, если бы я написал фильтрацию явно:
Users.objects.filter(assignments__status__in=["Approved", "Accepted"]).all()
Что бы сделать INNER JOIN
и WHERE status IN (Approved, Accepted)
.
Итак, мой вопрос заключается в следующем. Есть ли способ выбрать пользователей путем фильтрации по заданиям с помощью пользовательских менеджеров Задания эффективно?
Это больше похоже на пользовательский поиск, мы можем сделать это с помощью подкласса CharField
:
from django.db.models import CharField
from django.db.models.lookups import In, Lookup
class StatusField(CharField):
STATUS_CHOICES = [('a', 'Approved'), ('z', 'Accepted')]
def __init__(self, *args, **kwargs):
kwargs.setdefault('choices', self.STATUS_CHOICES)
super().__init__(*args, **kwargs)
class Active(Lookup):
lookup_name = 'active'
def as_sql(self, compiler, connection):
expr = In(self.lhs, ['a', 'z'])
if not self.rhs:
expr = ~expr
return expr.as_sql(compiler, connection)
StatusField.register_lookup(Active)
и теперь мы можем использовать StatusField
вместо него:
class Assignment(models.Model):
user = models.ForeignKey(setting.AUTH_USER_MODEL, related_name='assignments')
status = StatusField()
# …
и отфильтруйте оба элемента с помощью:
Assignment.objects.filter(status__active=True) # active assignment
User.objects.filter(assignments__active=False) # at last oneinactive asignment
, что позволяет использовать более декларативную фильтрацию.
Note: It is normally better to make use of the
settings.AUTH_USER_MODEL
[Django-doc] to refer to the user model, than to use theUser
model [Django-doc] directly. For more information you can see the referencing theUser
model section of the documentation.