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'
Как этого можно достичь?
2 ответов:
В настоящее время я нашел решение только с помощью необработанного запроса.
Упрощенный запрос для
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-запрос в метод querysetraw. Мои модели пользователей: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'))
Comments