【问题标题】:Filter GenericForeignKey by list of different model objects按不同模型对象的列表过滤 GenericForeignKey
【发布时间】:2021-10-23 13:53:11
【问题描述】:

我正在尝试按用户或任何其他模型对象构建活动日志,用户可以关注或订阅这些日志。

这些是与记录活动相关的模型:

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 关注用户 Loki // 用户关注用户
  • Thor 关注用户 Stark // 用户关注用户
  • Thor 关注 Avenger 项目 // 用户关注项目

假设这些是活动:

  • Nick Fury 创建了一个项目“Shield”
  • Nick Fury 创建了一个项目“复仇者联盟”
  • Stark 创建了一个项目“Mark II”
  • 复仇者联盟添加了愿景
  • 奇异博士创建了一个项目 Dormammu

我可以得到用户雷神

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>]>

现在要获取用户 Thor 所关注的活动列表,我尝试了以下操作:

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

Activity.objects.filter(q)

但是它抛出了一个错误:

FieldError:字段“演员”不生成自动反转 关系,因此不能用于反向查询。如果它是一个 GenericForeignKey,考虑添加一个 GenericRelation。

我可以获取单个对象的所有活动,然后是用户 Thor:

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>]>

但是我怎样才能从上述活动中获得 Thor 所关注的所有活动:

  • Nick Fury 创建了一个项目“复仇者联盟”
  • Stark 创建了一个项目“Mark II”
  • 复仇者联盟添加了愿景

更新

按要求添加用户模型:

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(max_length=255, unique=True)
    fullname = models.CharField(max_length=255, validators=[validate_fullname])
    is_active = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    date_joined = models.DateTimeField(auto_now_add=True)

    objects = UserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["fullname"]

    def __str__(self):
        return self.email

【问题讨论】:

  • 可以分享一下用户模型吗?
  • @bdbd 嗨!我已经更新并分享了用户模型。

标签: django django-models django-queryset


【解决方案1】:

简单地说,GenericForeignKey 是两个字段(ContentType 的外键和存储相关主键的字段)的组合,以指向特定对象。查看您的代码:

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

Activity.objects.filter(q)

这种过滤不适用于GenericForeignKey,基本上是因为如果这样做,如果有人尝试访问相关属性,Django 将不得不执行以下任务:

  • 进行多个连接
  • 找出要加入哪些表以免出错

这将是相当复杂的,并且有几个问题,例如如果连接太多,查询时间会很长等。

此外,您的代码有些不正确,因为它过滤了与 Follow 对象相关的 Activity 对象,而不是 followed 对象。

要解决您的问题,您可以改为过滤 actor_content_typeactor_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))

【讨论】:

    猜你喜欢
    • 2014-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-02
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 1970-01-01
    相关资源
    最近更新 更多