Фильтр 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))