Фильтр django для объектов, содержащих подмножества отношения «один ко многим» - PullRequest
0 голосов
/ 24 октября 2018

В моей базе данных есть пользовательские объекты с двумя полями «многие ко многим» (сообщения и последующие), которые оба содержат поле «многие ко многим», относящиеся к другой теме объекта.

class User():
    messages = ManyToManyField('Message', related_name='users', blank=True, null=True)
    following = ForeignKey('Following', related_name='users', blank=True, null=True) 

class Message():
    date = DateField(blank=True, null=True)
    content = TextField(blank=True, null=True)
    topics = ManyToManyField('Topic', related_name='messages', blank=True, null=True)

class Following():
    name = CharField(max_length=255, blank=True, null=True)
    description = CharField(max_length=255, blank=True, null=True)
    topics = ManyToManyField('Topic', related_name='following', blank=True, null=True)

class Topic():
    name = CharField(max_length=255, blank=True, null=True)
    source = CharField(max_length=255, blank=True, null=True)

Я хочу отфильтроватьдля всех «пользователей», которым прикреплены «сообщения», которые не содержат всех тем, прикрепленных к «следующим» объектам пользователя.

Сейчас я использую цикл, чтобы выполнить это:

users = set()
for user in User.objects.filter(messages__isnull=False, following__isnull=False).iterator():
    if not set(user.following.values_list('topics', flat=True))
               ).issubset(set(user.messages.values_list('topics', flat=True)):
       users.add(user.pk)

Есть ли способ выполнить то же самое с помощью одного запроса?

---- РЕДАКТИРОВАТЬ ----

Что у меня есть это:

User.objects.filter(following__isnull=False
).annotate(following_count=Count('following__topics', distinct=True)
).filter(following__topics__exact=F('message__topics')
).annotate(missing_topics=ExpressionWrapper(
    F('following_count') - Count('message__topics', distinct=True),
                                 IntegerField())
).filter(missing_topics__gt=0)

Если есть лучший способ сделать это или есть причины, по которым я совершенно определенно не должен делать это таким образом, что это?

---- EDIT ----

Этот вопрос помог мне понять и использовать Ответ Хокена Лида

Это моя новая модель и мой новый запрос:

class User():
    messages = ManyToManyField('Message', related_name='users', blank=True, null=True)
    following = ManyToManyField('Topic', through='Following', related_name='users', blank=True, null=True) 

class Message():
    date = DateField(blank=True, null=True)
    content = TextField(blank=True, null=True)
    topics = ManyToManyField('Topic', related_name='messages', blank=True, null=True)

class Following():
    name = CharField(max_length=255, blank=True, null=True)
    description = CharField(max_length=255, blank=True, null=True)
    user = ForeignKey('User', related_name='following', blank=True, null=True)
    topic = ForeignKey('Topic', related_name='following', blank=True, null=True)

class Topic():
    name = CharField(max_length=255, blank=True, null=True)
    source = CharField(max_length=255, blank=True, null=True)



User.objects.filter(~Q(messages__topics__in=F('following'))
).values('id').annotate(missing_topics=Count('following__topics', distinct=True))

1 Ответ

0 голосов
/ 24 октября 2018

Это должно быть возможно при использовании подзапроса.

Сначала убедитесь, что Following.topics использует другое имя, отличное от Messages.topics.

class Following(models.Model):
    topics = ManyToManyField('Topic', related_name='following')

Тогда можно создать подзапрос .Примерно так:

from django.db.models import OuterRef, Subquery
user_following_topic = Topic.objects.filter(following__users=OuterRef('pk'))
User.objects.exclude(messages__topics__in=Subquery(user_following_topics.values('pk')))

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

С другой стороныЯ не очень понимаю вашу структуру базы данных.Кажется, вы используете отношения m2m, где внешние ключи могут быть более подходящими и простыми.Чем сложнее ваши отношения, тем сложнее создать такой сложный запрос.А запросы с большим количеством соединений с базами данных могут быть очень медленными, поскольку им, возможно, придется обрабатывать огромные объемы данных по сравнению с простыми запросами.

Например, вместо использования реализаций m2m, Following будет иметь больше смысладля меня вот так:

class Following():
    topic = ForeignKey('Topic', on_delete=models.CASCADE)
    user = ForeignKey('User', on_delete=models.CASCADE)
    client = models.CharField(max_length=255, blank=True, null=True)
    duration = fields.DateRangeField(blank=False, null=False)

Итак, в основном сквозная модель, как объясняется в документах django о связях моделей , где есть похожий пример.

...