我想构建一个类似Quora或Medium的网络应用,用户可以关注用户或某些主题。
例如: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以获取以下用户的活动。
答案 0 :(得分:6)
您应该按ID过滤。
首先获取要在
上过滤的对象的IDfollowing_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
正在做的事情那么您可以在ForeignKey
和Follow
Person
到System
请注意,我正在使用此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))