Использование пользовательского менеджера для фильтрации по обратным связям

У меня есть набор пользователей и набор заданий, которые подает каждый пользователь.

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 the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Вернуться на верх