如何使用对象列表过滤多个字段
How to filter multiple fields with list of objects
我想构建一个类似 Quora or Medium 的网络应用程序,用户可以在其中关注用户或某些主题。
例如:用户 A 正在关注(用户 B、用户 C、标签-健康、标签-财务)。
这些是模型:
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 '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.
如何使用用户列表和用户关注的标签列表过滤唯一的活动?具体来说,我将如何使用对象列表过滤 GenericForeignKey 以获取以下用户的活动。
您可以分别查询用户和标签,然后将它们结合起来以获得您要查找的内容。请像下面这样做,如果可行请告诉我..
usrs = Activity.objects.filter(actor__in=following_user)
tags = Activity.objects.filter(tags__in=following_tag)
result = usrs | tags
如 documentation 中所述:
Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. Because a GenericForeignKey isn’t a normal field object, these examples will not work:
您可以按照错误消息告诉您的内容进行操作,我认为您必须添加一个 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())
并且您可以搜索 'persons' 后的标签如下:
Person.objects.filter(follow_tag__in=[<tag_ids>])
这同样适用于作者,您可以使用查询集对您的查询执行 OR、AND 等操作。
如果您希望更多模型能够关注标签或作者,例如 System
,也许您可以创建一个 Following
模型来做同样的事情 Person
这样做然后你可以在 Person
和 System
中添加一个 ForeignKey
到 Follow
请注意,我正在使用此 Person
来满足此 recomendation。
您应该只按 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
接受模型 class 和模型实例
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))
您可以使用注释将两个主键连接为一个字符串,然后使用它来过滤您的查询集。
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))
docs
的建议并不是坏事。
问题是,当您创建 Activities
时,您将 auth.User
用作 actor
,因此您无法将 GenericRelation
添加到 auth.User
(也许你可以通过猴子修补它,但这不是一个好主意).
那你能做什么?
@Sardorbek Imomaliev 解决方案非常好,如果将所有这些逻辑放入自定义 QuerySet
class 中,您可以使它变得更好。 (想法是实现 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. actor
你真的需要 GenericForeignKey
吗?我不知道你的业务逻辑,所以你可能知道,但是只使用常规的 FK
作为演员(就像 tags
一样)将可以像 actor__in=users_following
这样的工作人员.
2. 您是否检查过是否没有相应的应用程序?已经解决您问题的软件包的一个示例是 django-activity-steam 检查它。
3. 如果你不使用 auth.User
作为 actor
你可以完全按照 docs
的建议 -> 添加GenericRelation
字段。事实上,你的 Relationship
class 适合这个目的,但我真的会把它重命名为 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)
我想构建一个类似 Quora or Medium 的网络应用程序,用户可以在其中关注用户或某些主题。
例如:用户 A 正在关注(用户 B、用户 C、标签-健康、标签-财务)。
这些是模型:
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 '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.
如何使用用户列表和用户关注的标签列表过滤唯一的活动?具体来说,我将如何使用对象列表过滤 GenericForeignKey 以获取以下用户的活动。
您可以分别查询用户和标签,然后将它们结合起来以获得您要查找的内容。请像下面这样做,如果可行请告诉我..
usrs = Activity.objects.filter(actor__in=following_user)
tags = Activity.objects.filter(tags__in=following_tag)
result = usrs | tags
如 documentation 中所述:
Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. Because a GenericForeignKey isn’t a normal field object, these examples will not work:
您可以按照错误消息告诉您的内容进行操作,我认为您必须添加一个 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())
并且您可以搜索 'persons' 后的标签如下:
Person.objects.filter(follow_tag__in=[<tag_ids>])
这同样适用于作者,您可以使用查询集对您的查询执行 OR、AND 等操作。
如果您希望更多模型能够关注标签或作者,例如 System
,也许您可以创建一个 Following
模型来做同样的事情 Person
这样做然后你可以在 Person
和 System
ForeignKey
到 Follow
请注意,我正在使用此 Person
来满足此 recomendation。
您应该只按 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
接受模型 class 和模型实例
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))
您可以使用注释将两个主键连接为一个字符串,然后使用它来过滤您的查询集。
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))
docs
的建议并不是坏事。
问题是,当您创建 Activities
时,您将 auth.User
用作 actor
,因此您无法将 GenericRelation
添加到 auth.User
(也许你可以通过猴子修补它,但这不是一个好主意).
那你能做什么?
@Sardorbek Imomaliev 解决方案非常好,如果将所有这些逻辑放入自定义 QuerySet
class 中,您可以使它变得更好。 (想法是实现 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. actor
你真的需要 GenericForeignKey
吗?我不知道你的业务逻辑,所以你可能知道,但是只使用常规的 FK
作为演员(就像 tags
一样)将可以像 actor__in=users_following
这样的工作人员.
2. 您是否检查过是否没有相应的应用程序?已经解决您问题的软件包的一个示例是 django-activity-steam 检查它。
3. 如果你不使用 auth.User
作为 actor
你可以完全按照 docs
的建议 -> 添加GenericRelation
字段。事实上,你的 Relationship
class 适合这个目的,但我真的会把它重命名为 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)