【问题标题】:How to filter multiple fields with list of objects如何使用对象列表过滤多个字段
【发布时间】:2016-10-27 18:56:51
【问题描述】:

我想构建一个像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:字段'actor'不会生成自动反向关系,因此不能用于反向查询。如果是 GenericForeignKey,请考虑添加 GenericRelation。

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

【问题讨论】:

  • OT:我成功地将这个包(在一个非平凡的 webapp 上的生产中)用于您描述的用例:github.com/bitmazk/django-object-events
  • 嗨@Aamu,请告诉我,如果这样做可以让您毫无问题地获得结果。Activity.objects.filter(actor__in=following_user) 有效。?
  • @simonecittadini 谢谢。我一定会检查一下。
  • @Trying2Learn 不,Activity.objects.filter(actor__in=following_user) 是引发错误的原因。

标签: django django-queryset


【解决方案1】:

您可以分别查询用户名和标签,然后将它们结合起来以获得您要查找的内容。请执行以下操作,如果可行,请告诉我..

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

result = usrs | tags

【讨论】:

  • Activity.objects.filter(actor__in=following_user) 这是抛出上述错误。 Activity.objects.filter(tags__in=following_tag) 工作正常。
  • 这个工作user_ids = following_user.values_list('id', flat=True) ... usrs = Activity.objects.filter(actor__in=user_ids)
  • Activity.objects.filter(actor__in=user_ids) 给了我同样的错误 (FieldError: Field 'actor' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation. )。
  • 您是否尝试过为它创建一个 GenericRelation? docs.djangoproject.com/en/1.9/ref/contrib/contenttypes/…
【解决方案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 相同的操作,然后您可以添加一个 @ 987654330@ 到 Follow 都在 PersonSystem

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

【讨论】:

  • 对不起,我有像你这样的模型,我忘记更新了。
  • 您想通过 GenericRelation 实现什么目标?
【解决方案3】:

您应该只按 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 在 cmets 中建议的那样。因为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))

【讨论】:

  • 我认为这也是要走的路,据我了解,GenericForeignKey 不会让您访问 QuerySet,因此您必须根据模型和对象 id 过滤一下自己像一个复合键。
  • 我只想提一下ContentType.objects.get_for_model 与模型类或模型实例一起使用。所以它完全可以:ContentType.objects.get_for_model(userA).
【解决方案4】:

您可以使用 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))

【讨论】:

    【解决方案5】:

    docs 的建议并不是一件坏事。

    问题在于,当您创建Activities 时,您将auth.User 用作actor,因此您不能将GenericRelation 添加到auth.User (也许您可以通过猴子-修补它,但这不是一个好主意)

    那么你能做什么呢?

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

    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 的建议进行操作-> 添加GenericRelation 字段。实际上,您的 Relationship 类适合此目的,但我真的会将其重命名为 UserProfile 或至少 UserRelation。考虑我们已将Relation 重命名为UserProfile,然后使用userprofile 创建新的Activities。这个想法是:

    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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-01
      • 1970-01-01
      相关资源
      最近更新 更多