使用内容对象字段列表过滤内容类型

时间:2017-01-03 19:18:17

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

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

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使用标签(#class,#school)
  • 创建了一个新的Post对象
  • UserB使用标签(#professor,#teacher)
  • 创建了一个新的Post对象
  • UserC使用标签(#school,#university)
  • 创建了一个新的Post对象
  • UserD使用标签(#university,#school)
  • 创建了一个新的Post对象

所以说我想用user_list=[UserA, UserC]tag_list = [#class, #teacher]

过滤活动

它应该返回:

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

要使用用户过滤活动,我可以这样查询:

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)。所以,我希望有一种更好的方法来优化这个过程。

3 个答案:

答案 0 :(得分:0)

我会从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)

希望它可以帮助..

答案 1 :(得分:0)

对于此方法,将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()

答案 2 :(得分:0)

我说你最好的选择是使用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&users=2 - 由用户1或用户2
  • 创建的所有内容
  • GET /activities?users=1&tags=class - 用户1使用标签#Class
  • 创建的所有内容
  • GET /activities?users=1&users=2&tags=class&tags=school - 用户1或用户2使用标签#Class或#School
  • 创建的所有内容