Django queryset прикрепить или аннотировать связанное поле объекта



Необходимо присоединить к QuerySet results связанное поле объекта.

Модели:

class User(models.Model):
    name = models.CharField(max_length=50)
    friends = models.ManyToManyField('self', through='Membership',
        blank=True, null=True, symmetrical=False)


class Membership(models.Model):
    status = models.CharField(choices=SOME_CHOICES, max_length=50)
    from_user = models.ForeignKey(User, related_name="member_from")
    to_user = models.ForeignKey(User, related_name="member_to")

Я могу это сделать:

>>> User.objects.all().values('name', 'member_from__status')
[{'member_from__status': u'accepted', 'name': 'Ann'}, {'member_from__status': u'thinking', 'name': 'John'}]

'member_from__status' содержит информацию, которая мне нужна. Но вместе с ним мне нужен и образцовый экземпляр.

Чего я хочу, так это:

>>> users_with_status = User.objects.all().do_something('member_from__status')
>>> users_with_status
[<User 1>, <User 2>]

>>> users_with_status[0] # <-- this is an object, so i can access to all its methods

Каждый экземпляр в queryset имеет поле 'member _ from_ _ status' с соответствующим значением:

>>> users_with_status[0].member_from__status
u'accepted'

Как этого можно достичь?

37   2  

2 ответов:

Вы можете использовать annotate и еще F

>>> from django.db.models import F
>>> u = User.objects.all().annotate(member_from__status=F('member_from__status'))
>>> u.member_from__status
'accepted'

Протестировано с Django 1.11

В настоящее время я нашел решение только с помощью необработанного запроса.

Упрощенный запрос для user.friends.all():

SELECT "users_user"."id", "users_user"."name", FROM "users_user" INNER JOIN "users_membership" ON ("users_user"."id" = "users_membership"."to_user_id") WHERE "users_membership"."from_user_id" = 10;

Как мы видим, users_membership таблица уже присоединился. Поэтому я копирую этот запрос и просто добавляю поле "users_membership"."status". Затем я создаю methon в моей модели friends_with_status и вставляю новый SQL-запрос в метод queryset raw. Мои модели пользователей:

class User(models.Model):
    name = models.CharField(max_length=50)
    friends = models.ManyToManyField('self', through='Membership',
        blank=True, null=True, symmetrical=False)


    def friends_with_status(self):
        return User.objects.raw('SELECT "users_membership"."status", "users_user"."id", "users_user"."name", FROM "users_user" INNER JOIN "users_membership" ON ("users_user"."id" = "users_membership"."to_user_id") WHERE "users_membership"."from_user_id" = %s;', [self.pk])

Теперь я использую это:

>>> user = User.objects.get(name="John")
>>> friends = user.friends_with_status()
>>> friends[0].status
'accepted'
>>> friends[1].status
'thinking'

P.S.

Конечно, это включает в себя все недостатки raw запроса: невозможно применить какие-либо далее queryset методы на нем, т. е. это не будет работать:

>>> friends = user.friends_with_status().filter()
>>> friends = user.friends_with_status().exclude()

И так далее. Кроме того, если я изменяю поля модели, я также должен изменить необработанный запрос.

Но, по крайней мере, такой подход дает мне то, что мне нужно в одном запросе.

Я думаю, будет полезно написать какой-нибудь метод аннотации, например Count или Avg, который позволит присоединять поля из Объединенной таблицы. Что-то вроде этого:

>>> from todo_my_annotations import JoinedField
>>> user = User.objects.get(name="John")
>>> friends = user.friends.annotate(status=JoinedField('member_from__status'))
    Ничего не найдено.

Добавить ответ:
Отменить.