如何使用对象列表过滤多个字段

时间:2016-06-25 06:05:20

标签: django django-queryset

我想构建一个类似QuoraMedium的网络应用,用户可以关注用户或某些主题。

例如:userA正在关注(userB,userC,tag-Health,tag-Finance)。

这些是模型:

class Relationship(models.Model):
    user = AutoOneToOneField('auth.user')
    follows_user = models.ManyToManyField('Relationship', related_name='followed_by')
    follows_tag = models.ManyToManyField(Tag)

class Activity(models.Model):
    actor_type = models.ForeignKey(ContentType, related_name='actor_type_activities')
    actor_id = models.PositiveIntegerField()
    actor = GenericForeignKey('actor_type', 'actor_id')
    verb = models.CharField(max_length=10)
    target_type = models.ForeignKey(ContentType, related_name='target_type_activities')
    target_id = models.PositiveIntegerField()
    target = GenericForeignKey('target_type', 'target_id')
    tags = models.ManyToManyField(Tag)

现在,这将给出以下列表:

following_user = userA.relationship.follows_user.all()
following_user
[<Relationship: userB>, <Relationship: userC>]
following_tag = userA.relationship.follows_tag.all()
following_tag
[<Tag: tag-job>, <Tag: tag-finance>]

过滤我试过这种方式:

Activity.objects.filter(Q(actor__in=following_user) | Q(tags__in=following_tag))

但由于actor是GenericForeignKey,我收到错误:

  

FieldError:Field&#39; actor&#39;不生成自动反向关系,因此不能用于反向查询。如果是GenericForeignKey,请考虑添加GenericRelation。

如何使用用户列表和用户关注的标记列表过滤唯一的活动?具体来说,我将如何使用对象列表过滤GenericForeignKey以获取以下用户的活动。

5 个答案:

答案 0 :(得分:6)

您应该按ID过滤。

首先获取要在

上过滤的对象的ID
following_user = userA.relationship.follows_user.all().values_list('id', flat=True)
following_tag = userA.relationship.follows_tag.all()

此外,您还需要对actor_type进行过滤。它可以像for example.

那样完成
actor_type = ContentType.objects.get_for_model(userA.__class__)

或@Todor在评论中建议。因为get_for_model接受模型类和模型实例

actor_type = ContentType.objects.get_for_model(userA)

而且你可以像这样过滤。

Activity.objects.filter(Q(actor_id__in=following_user, actor_type=actor_type) | Q(tags__in=following_tag))

答案 1 :(得分:3)

docs所暗示的并不是一件坏事。

问题在于,在创建Activities时,您使用auth.User作为actor,因此无法将GenericRelation添加到auth.User < em>(也许你可以通过猴子修补它,但这不是一个好主意)。

那么你能做什么?

@Sardorbek Imomaliev 解决方案非常好,如果将所有这些逻辑放入自定义QuerySet类中,您可以做得更好。 (想法是实现干燥和可重用性)

class ActivityQuerySet(models.QuerySet):
    def for_user(self, user):
        return self.filter(
            models.Q(
                actor_type=ContentType.objects.get_for_model(user),
                actor_id__in=user.relationship.follows_user.values_list('id', flat=True)
            )|models.Q(
                tags__in=user.relationship.follows_tag.all()
            )
        )

class Activity(models.Model):
    #..
    objects = ActivityQuerySet.as_manager()

#usage
user_feed = Activity.objects.for_user(request.user)

但还有别的吗?

1。 GenericForeignKey你真的需要actor吗?我不知道你的业务逻辑,所以可能你这样做,但是只使用常规的FK作为演员(就像tags一样)可以像actor__in=users_following这样做一些工作人员。

2。您是否检查过是否有应用?已经解决问题的软件包的一个示例是django-activity-steam检查它。

3。如果您未将auth.User用作actor,则可以完全按照docs的建议 - &gt;添加GenericRelation字段。实际上,您的Relationship类适用于此目的,但我确实会将其重命名为UserProfile或至少UserRelation。考虑我们已将Relation重命名为UserProfile,我们使用Activities创建新的userprofile。这个想法是:

class UserProfile(models.Model):
    user = AutoOneToOneField('auth.user')
    follows_user = models.ManyToManyField('UserProfile', related_name='followed_by')
    follows_tag = models.ManyToManyField(Tag)

    activies_as_actor = GenericRelation('Activity',
        content_type_field='actor_type',
        object_id_field='actor_id',
        related_query_name='userprofile'
    )


class ActivityQuerySet(models.QuerySet):
    def for_userprofile(self, userprofile):
        return self.filter(
            models.Q(
                userprofile__in=userprofile.follows_user.all()
            )|models.Q(
                tags__in=userprofile.relationship.follows_tag.all()
            )
        )


class Activity(models.Model):
    #..
    objects = ActivityQuerySet.as_manager()

#usage

#1st when you create activity use UserProfile
Activity.objects.create(actor=request.user.userprofile, ...)

#2nd when you fetch. 
#Check how `for_userprofile` is implemented this time
Activity.objects.for_userprofile(request.user.userprofile)

答案 2 :(得分:2)

documentation中所述:

  

由于GenericForeignKey的实现方式,您不能通过数据库API直接将这些字段用于过滤器(例如filter()和exclude())。因为GenericForeignKey不是普通的字段对象,所以这些示例不起作用:

您可以按照错误消息告诉您的内容,我认为您必须添加GenericRelation关系才能执行此操作。我没有这方面的经验,我必须研究它,但是......

我个人认为这个解决方案太复杂了,无法实现。如果只有user模型可以跟随一个或多个作者,为什么不在其上包含ManyToManyField。它会是这样的:

class Person(models.Model):
    user = models.ForeignKey(User)
    follow_tag = models.ManyToManyField('Tag')
    follow_author = models.ManyToManyField('Author')

您可以像这样查询每个人的所有跟踪标记活动:

Activity.objects.filter(tags__in=person.follow_tag.all())

您可以按照以下标记搜索“人物”:

Person.objects.filter(follow_tag__in=[<tag_ids>])

这同样适用于作者,您可以使用查询集对查询执行OR,AND等。

如果您希望更多模型能够关注标记或作者,请说System,也许您可​​以创建一个Following模型来完成同样的事情Person正在做的事情那么您可以在ForeignKeyFollow

中添加PersonSystem

请注意,我正在使用此Person来满足此recomendation

答案 3 :(得分:1)

您可以单独查询用户和标签,然后将它们组合起来以获得您要查找的内容。请做下面的事情,如果有效,请告诉我。

usrs = Activity.objects.filter(actor__in=following_user)    
tags = Activity.objects.filter(tags__in=following_tag)

result = usrs | tags

答案 4 :(得分:1)

您可以使用annotate将两个主键作为单个字符串连接,然后使用它来过滤您的查询集。

from django.db.models import Value, TextField
from django.db.models.functions import Concat

following_actor = [
    # actor_type, actor
    (1, 100),
    (2, 102),
]
searchable_keys = [str(at) + "__" + str(actor) for at, actor in following_actor]

result = MultiKey.objects.annotate(key=Concat('actor_type', Value('__'), 'actor_id', 
                                              output_field=TextField()))\
    .filter(Q(key__in=searchable_keys) | Q(tags__in=following_tag))