Фильтр GenericForeignKey по списку различных объектов модели

Я пытаюсь построить журнал активности по пользователю или любому другому объекту модели.

Вот модели, связанные с протоколированием действий:

class Activity(models.Model):
    """
    An activity log : Actor acts on target
    Actor can be a User or a model Object
    """
    actor_content_type = models.ForeignKey(
        ContentType, on_delete=models.CASCADE, related_name="actor_type")
    actor_object_id = models.PositiveIntegerField()
    actor = GenericForeignKey('actor_content_type', 'actor_object_id')
    description = models.TextField()
    target_content_type = models.ForeignKey(
        ContentType, on_delete=models.CASCADE, related_name="target_type")
    target_object_id = models.PositiveIntegerField()
    target = GenericForeignKey('target_content_type', 'target_object_id')


class Follow(models.Model):
    """
    A user can follow any User or model objects.
    """
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    follow_object = GenericForeignKey('content_type', 'object_id')

Предположим:

  • Тор следует за пользователем Локи // Пользователь следует за пользователем
  • Thor следует за пользователем Stark // Пользователь следует за пользователем
  • Тор следует за проектом Мститель // Пользователь следует за проектом

Предположим, что это деятельность:

  • Ник Фьюри Создал проект "Щит"
  • Ник Фьюри создал проект "Мстители"
  • Старк создал проект "Mark II"
  • "Мстители" добавили Вижена
  • Д-р Стрэндж создал проект Дормамму

Я могу получить пользователя Thor:

thor = User.objects.get(email="thor@gmail.com")

Получаем список пользователей/объектов, за которым следует Thor:

thor.follow_set.all()
<QuerySet [<Follow: thor@gmail.com follows loki@gmail.com>, <Follow: thor@gmail.com follows Stark@gmail.com>, <Follow: thor@gmail.com follows Avengers>]>

Теперь, чтобы получить список действий, за которыми следует пользователь Тор, я попробовал следующее:

Activity.objects.filter(actor__in=thor.follow_set.all())

Но выдает ошибку:

FieldError: Поле 'actor' не генерирует автоматическое обратное отношение и поэтому не может быть использовано для обратного запроса. Если это GenericForeignKey, рассмотрите возможность добавления GenericRelation.

Я могу получить все действия для одного объекта, за которым следует пользователь Тор:

followed_last = thor.follow_set.last().follow_object

q = (Q(actor_content_type=ContentType.objects.get_for_model(followed_last), actor_object_id=followed_last.id)| Q(target_content_type=ContentType.objects.get_for_model(followed_last), target_object_id=followed_last.id))

Activity.objects.filter(q)
<QuerySet [<Activity: Avengers Added vision@gmail.com>, <Activity: nickfury@gmail.om Created Avengers>]>

Но как я могу получить все действия Тора, следующие из вышеперечисленных действий:

  • Ник Фьюри создал проект "Мстители"
  • Старк создал проект "Mark II"
  • "Мстители" добавили Вижена

А GenericForeignKey простыми словами - это композиция двух полей (внешний ключ к ContentType и поле для хранения связанного первичного ключа) для указания на определенный объект. Глядя на ваш код:

q = (Q(actor__in=thor.follow_set.all())| Q(target__in=thor.follow_set.all())

Activity.objects.filter(q)

Подобная фильтрация не работает с GenericForeignKey в основном потому, что если бы она работала, то в случае попытки доступа к связанным атрибутам Django пришлось бы выполнять задачи типа:

  • Сделайте несколько объединений
  • Выясните какие таблицы соединять, чтобы не получить ошибок

Это было бы довольно сложно и имело бы несколько проблем, таких как длительное время выполнения запросов при наличии слишком большого количества объединений и т.д.

Также ваш код несколько некорректен, поскольку он фильтрует Activity объекты, связанные с Follow объектами, а не следующий объект.

Чтобы решить вашу проблему, вы можете вместо этого фильтровать по actor_content_type, actor_object_id и т.д.:

follows = thor.follow_set.all()

filters = [Q(actor_content_type=follow.content_type)
     & Q(actor_object_id=follow.object_id)
     | Q(target_content_type=follow.content_type)
     & Q(target_object_id=follow.object_id) for follow in follows]

Activity.objects.filter(Q(*filters, _connector=Q.OR))
Вернуться на верх