【问题标题】:Filter a contenttype with list of content object's field使用内容对象的字段列表过滤内容类型
【发布时间】:2017-05-17 23:24:02
【问题描述】:

我创建了一个应用程序,用户可以在其中发布相关标签:

class Tag(models.Model):
    name = models.CharField(max_length=255, unique=True)

class Post(models.Model):
    user = models.ForeignKey(User)
    body = models.TextField()
    tags = models.ManyToManyField(Tag)
    pub_date = models.DateTimeField(default=timezone.now)
    activity = GenericRelation(Activity, related_query_name="posts")

class Photo(models.Model):
    user = models.ForeignKey(User)
    file = models.ImageField()
    tags = models.ManyToManyField(Tag)
    pub_date = models.DateTimeField(default=timezone.now)
    activity = GenericRelation(Activity, related_query_name="photos")

class Activity(models.Model):
    actor = models.ForeignKey(User)
    verb = models.PositiveIntegerField(choices=VERB_TYPE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    pub_date = models.DateTimeField(default=timezone.now)

我想要做的是获取/过滤最多 5 个最新/最近的 Activity 对象,其中包含用户列表,以及来自 Post 对象标签字段列表的标签列表,并使用 django-rest-framework 返回 json 以查看在客户端。

例如活动:

  • UserA 创建了一个带有标签的新 Post 对象(#class, #school)
  • UserB 创建了一个带有标签的新 Post 对象(#professor, #teacher)
  • UserC 创建了一个带有标签的新 Post 对象(#school, #university)
  • UserD 创建了一个带有标签的新 Post 对象(#university, #school)

假设我想用user_list=[UserA, UserC]tag_list = [#class, #teacher] 过滤活动

它应该返回:

  • UserA 创建了一个带有标签的新 Post 对象(#class, #school)
  • UserC 创建了一个带有标签的新 Post 对象(#school, #university)
  • UserB 创建了一个带有标签的新 Post 对象(#professor, #teacher)

要过滤带有用户的Activity,我可以这样查询:

Activity.objects.filter(actor__in=user_list)

但是,如何使用 content_object 的(即 Post 或 Photo)字段(即 Post.tags 或 Photo.tags)过滤 Activity?现在我正在这样做:

Activity.objects.filter(posts__tags__in=tag_l)
Activity.objects.filter(photos__tags__in=tags)

总而言之,如果我需要包含用户列表和标签列表的活动,我必须这样做:

activites = Activity.objects.filter(
    Q(actor__in=user_list) |
    Qposts__tags__in=tag_list) |
    Q(photos__tags__in=tag_list)
)

但假设会有两个以上的 ContentType 模型类,那么我必须再次添加另一个 Q(moreModel__tags__in=tag_list)。所以,我希望有更好的方法来优化这个过程。

【问题讨论】:

    标签: django django-models django-rest-framework django-queryset


    【解决方案1】:

    我会给你一个来自django updown的例子,它有一个类似的模型:https://github.com/weluse/django-updown/blob/master/updown/models.py#L31

    >>> from updown.models import Vote
    >>> Vote.objects.first()
    <Vote: john voted 1 on Conveniently develop impactful e-commerce>
    >>> v = Vote.objects.first()
    >>> v.content_object
    <Thread: Conveniently develop impactful e-commerce>
    >>> v.content_object.__class__
    <class 'app_forum.models.Thread'>
    >>> [ v.content_type for v in Vote.objects.all() if v.content_object.__class__.__name__ == 'Thread' ]
    [<Thread: Conveniently develop impactful e-commerce>, <Thread: Quickly evisculate exceptional paradigms>]
    >>> 
    # You can also use with
    >>> user_ids = [ u.content_type.id for u in Vote.objects.all() if u.content_object.__class__.__name__ == 'User' ]
    >>> user_ids
    [1, 52, 3, 4]
    >>> from django.contrib.auth.models import User
    >>> User.objects.filter(pk__in=user_ids)
    [<User: John>, <User: Alex>, <User: Roboto>, <User: Membra>]
    >>>
    >>> from django.contrib.contenttypes.models import ContentType
    >>> ContentType.objects.get_for_model(v.content_object)
    <ContentType: Detail Thread>
    >>> 
    

    您也可以使用ContentType.objects.get_for_model(model_instance),例如:https://github.com/weluse/django-updown/blob/master/updown/fields.py#L70

    在你的问题中,也许可以用这个..

    >>> photo_ids = [ ac.content_type.id for ac in Activity.objects.all() if ac.content_object.__class__.__name__ == 'Photo' ]
    >>> Activity.objects.filter(content_type__id__in=photo_ids)
    # or
    >>> photo_ids = [ ac.content_type.id for ac in Activity.objects.all() if content_type.model_class().__name__ == 'Photo']
    >>> Activity.objects.filter(content_type__id__in=photo_ids)
    

    希望对你有帮助..

    【讨论】:

    • @Robin hello robin,现在,在我当前的项目中,遇到了与您的问题类似的情况...我创建了一个具有通知系统的论坛...因此,通过创建 @987654328 来处理它@ and sender field.. 希望我的 sn-ps 对你有用:gist.github.com/agusmakmun/6607cbd13b7e6b06a0da97cd18b2ef22
    【解决方案2】:

    对于此方法,将related_query_name 设置为模型的小写名称或模型的verbose_name。

    您可以先过滤掉Activity 模型中存在的内容类型。

    content_types = ContentType.objects.filter(activity__id__isnull=False)
    

    现在使用这些内容类型来构建查找。

    q = Q(actor__in=user_list)
    for content_type in content_types:
        arg = content_type.name + '__tags__in'
        kwargs = {arg: tag_list}
        q = q | Q(**kwargs)
    

    现在您可以使用此查找过滤活动。

    activities = Activity.objects.filter(q).distinct()
    

    【讨论】:

    • 嗨,这不会击中数据库两次吗?此外,我只想获得最多 5 个最新的活动对象,但是在您的第一个过滤器中,它似乎在查询整个 ContentType 对象?如果我在这里错了,请纠正我?
    • 嗨,是的,你在这两点上都是对的。我使用第一个过滤器来查询所有内容类型。如果您已经知道要查询的不同模型的 related_query_name,则可以将名称传递到列表中并通过此方法构造查找。这将跳过第一个查询。例如。 content_types = ['posts', 'photos']。您可以创建活动模型的自定义管理器方法,并将此列表作为参数传递,这将决定要查询的反向关系。
    【解决方案3】:

    我想说你最好的选择是使用来自Django filter 的过滤器(链接到其余框架文档),特别是ModelMultipleChoiceFilter。我假设您已经有一个 ActivityViewSetActivity 模型一起使用。

    首先,您需要创建一个django_filters.FilterSet,可能在一个新文件中,例如filters.py,然后设置ModelMultipleChoiceFilter,如下所示:

    import django_filters
    
    from .models import Activity, Tag, User
    
    class ActivityFilterSet(django_filters.FilterSet):
        tags = django_filters.ModelMultipleChoiceFilter(
            name='content_object__tags__name',
            to_field_name='name',
            lookup_type='in',
            queryset=Tag.objects.all()
        )
        users = django_filters.ModelMultipleChoiceFilter(
            name='content_object__user__pk',
            to_field_name='pk',
            lookup_type='in',
            queryset=User.objects.all()
        )
    
        class Meta:
            model = Activity
            fields = (
                'tags',
                'users',
            )
    

    然后你要告诉你的视图集使用那个过滤器集:

    from .filters import ActivityFilterSet
    # ...
    class ActivityViewSet(GenericViewSet):
        # all your existing declarations, eg.,
        # serializer_class = ActivitySerializer
        # ...
        filter_class = ActivityFilterSet
        # ...
    

    完成此操作后,您将能够使用 GET 参数过滤结果,例如,

    • GET /activities?users=1 – 用户 1 创建的所有内容
    • GET /activities?users=1&amp;users=2 – 用户 1 或用户 2 创建的所有内容
    • GET /activities?users=1&amp;tags=class – 用户 1 使用标签 #Class 创建的所有内容
    • GET /activities?users=1&amp;users=2&amp;tags=class&amp;tags=school – 用户 1 或用户 2 创建的所有内容,带有 #Class 或 #School 标签
    • 等等

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-28
      • 2013-06-09
      • 2022-10-04
      • 2012-12-04
      • 1970-01-01
      • 2015-11-07
      相关资源
      最近更新 更多