Как фильтровать несколько полей со списком объектов

Я хочу создать webapp, как очередную статью или средний, где пользователь может следить за пользователями или некоторые темы.

например: userA следующий (userB, userC, tag-здоровье, tag-финансы).

эти модели:

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))

но поскольку актер является GenericForeignKey, я получаю ошибку:

FieldError: поле "актер" не генерирует автоматическое обратное отношение и поэтому не может использоваться для обратного запроса. Если это GenericForeignKey, рассмотрите возможность добавления GenericRelation.

как я могу фильтровать действия, которые будут уникальными, со списком пользователей и списком тегов, которые пользователь следует? Чтобы быть конкретным, как я буду фильтровать GenericForeignKey со списком объектов, чтобы получить действия следующих пользователей.

5 ответов


какого docs предполагают, что это не плохо.

проблема в том, что при создании Activities вы используете auth.User как actor, поэтому вы не можете добавить GenericRelation to auth.User (ну, может быть, вы можете, исправляя его обезьяной, но это не очень хорошая идея).

так что вы можете сделать?

@Sardorbek Imomaliev решение очень хорошо, и вы можете сделать его еще лучше, если вы поместите всю эту логику в custom QuerySet класса. (идея в том, чтобы достигнуть сухого-Несс и reausability)

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-активность-steam проверьте это.

3. если вы не используете auth.User как actor вы можете сделать именно то, что docs предлагаю -> добавление


вы должны просто фильтровать по идентификаторам.

сначала получите идентификаторы объектов, которые вы хотите отфильтровать на

following_user = userA.relationship.follows_user.all().values_list('id', flat=True)
following_tag = userA.relationship.follows_tag.all()

Также вам нужно будет фильтровать по actor_type. Это можно сделать так например.

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))

как говорится в документация:

из-за того, как реализован GenericForeignKey, вы не можете использовать такие поля непосредственно с фильтрами (filter() и exclude (), например) через API базы данных. Поскольку GenericForeignKey не является обычным объектом поля, эти примеры не будут работать:

вы можете следить за тем, что сообщение об ошибке говорит вам, я думаю, вам придется добавить отношение GenericRelation для этого. Я не у меня есть опыт в этом, и я должен изучить его, но...

лично я думаю, что это решение слишком сложно для того, что вы пытаетесь достичь. Если бы только user модель может следовать тегу или авторам, почему бы не включить ManyToManyField на нем. Это было бы примерно так:--15-->

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>])

то же самое относится к авторам, и вы можете использовать querysets для выполнения OR, AND и т. д.. по вашим запросам.

если вы хотите, чтобы больше моделей могли следовать тегу или автору, скажите System, может быть, вы могли бы создать Following модель, которая делает то же самое Person делает, а затем вы можете добавить ForeignKey to Follow обоих в Person и System

обратите внимание, что я использую этот Person, чтобы встретить этот рекомендация.


вы можете запросить отдельно для usrs и тегов, а затем объединить их, чтобы получить то, что вы ищете. Пожалуйста, сделать что-то вроде ниже и дайте мне знать, если это работает..

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

result = usrs | tags

вы можете использовать annotate для объединения двух первичных ключей в одну строку, а затем использовать ее для фильтрации вашего queryset.

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))