Django REST Framework Group по полям и добавьте дополнительное содержимое
У меня есть модель бронирования билетов
class Movie(models.Model):
name = models.CharField(max_length=254, unique=True)
class Show(models.Model):
day = models.ForeignKey(Day)
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class MovieTicket(models.Model):
show = models.ForeignKey(Show)
user = models.ForeignKey(User)
booked_at = models.DateTimeField(default=timezone.now)
Я хотел бы фильтровать MovieTicket с его полем user
и группировать их в соответствии с его полем show
и заказывать их по забронированному времени. И ответьте с данными json
с использованием инфраструктуры Django REST следующим образом:
[
{
show: 4,
movie: "Lion king",
time: "07:00 pm",
day: "23 Apr 2017",
total_tickets = 2
},
{
show: 7,
movie: "Gone girl",
time: "02:30 pm",
day: "23 Apr 2017",
total_tickets = 1
}
]
Я пробовал этот путь:
>>> MovieTicket.objects.filter(user=23).order_by('-booked_at').values('show').annotate(total_tickets=Count('show'))
<QuerySet [{'total_tickets': 1, 'show': 4}, {'total_tickets': 1, 'show': 4}, {'total_tickets': 1, 'show': 7}]>
Но его не группировка в соответствии с шоу. Также как я могу добавить другие связанные поля (т.е. show__movie__name
, show__day__date
, show__time
)
Ответы
Ответ 1
Вам нужно сгруппировать шоу и затем подсчитать общее количество билетов в кино.
MovieTicket.objects.filter(user=23).values('show').annotate(total_tickets=Count('show')).values('show', 'total_tickets', 'show__movie__name', 'show__time', 'show__day__date'))
Используйте этот класс serilizer для указанного набора запросов. Он даст требуемый вывод json.
class MySerializer(serializers.Serializer):
show = serailizer.IntegerField()
movie = serializer.StringField(source='show__movie__name')
time = serializer.TimeField(source='show__time')
day = serializer.DateField(source='show__day__date')
total_tickets = serializer.IntegerField()
Невозможно заказать order_by reserved_at, поскольку эта информация теряется, когда мы группируем шоу. Если мы закажем группу reserved_at, это произойдет по уникальным забронированным временам и покажет идентификаторы, и именно поэтому количество билетов достигнет 1. Без order_by вы получите правильный счет.
EDIT:
используйте этот запрос:
queryset = (MovieTicket.objects.filter(user=23)
.order_by('booked_at').values('show')
.annotate(total_tickets=Count('show'))
.values('show', 'total_tickets', 'show__movie__name',
'show__time', 'show__day__date')))
Вы не можете комментировать в аннотированном поле. Таким образом, вы найдете общее количество билетов на python. Чтобы вычислить total_tickets count для уникальных идентификаторов показа:
tickets = {}
for obj in queryset:
if obj['show'] not in tickets.keys():
tickets[obj['show']] = obj
else:
tickets[obj['show']]['total_tickets'] += obj['total_tickets']
окончательный список объектов, которые вам нужны, tickets.values()
Такой же сериализатор, указанный выше, может использоваться с этими объектами.
Ответ 2
Я хотел бы фильтровать MovieTicket с его полем пользователя и группировать их в соответствии с его выставочным полем, и заказать их по забронированному времени.
Этот queryset
даст вам именно то, что вы хотите:
tickets = (MovieTicket.objects
.filter(user=request.user)
.values('show')
.annotate(last_booking=Max('booked_at'))
.order_by('-last_booking')
)
И ответьте с помощью json-данных, используя инфраструктуру Django rest, такую как: [ { показать на карте: 4, фильм: "Царь Льва", время: "19:00", день: "23 апреля 2017 года", total_tickets = 2 }, { показать на карте: фильм: "Уведенная девушка", время: "02:30 вечера", день: "23 апреля 2017 года", total_tickets = 1 } ]
Ну, эти json-данные не совпадают с запросами, которые вы описали. Вы можете добавить total_tickets
, расширив аннотацию и show__movie__name
в предложение .values
: это изменит группировку, чтобы показать + имя_ролика, но поскольку у шоу есть только одно имя фильма, это не имеет значения.
Однако вы не можете добавить show__day__date
и show__time
, потому что у одного шоу есть несколько дат-времени, и какой из них вы хотите от группы? Например, вы можете получить максимальные значения day
и time
, но это не гарантирует, что в этот день + время будет шоу, потому что это разные поля, не связанные друг с другом. Таким образом, последняя попытка может выглядеть так:
tickets = (MovieTicket.objects
.filter(user=request.user)
.values('show', 'show__movie__name')
.annotate(
last_booking=Max('booked_at'),
total_tickets=Count('pk'),
last_day=Max('show__day'),
last_time=Max('show__time'),
)
.order_by('-last_booking')
)
Ответ 3
Вы можете попробовать это.
Show.objects.filter(movieticket_sets__user=23).values('id').annotate(total_tickets=Count('movieticket_set__user')).values('movie__name', 'time', 'day').distinct()
ИЛИ
Show.objects.filter(movieticket_sets__user=23).values('id').annotate(total_tickets=Count('id')).values('movie__name', 'time', 'day').distinct()
Ответ 4
Я рисую модель базы данных, чтобы быть легко запоминающейся. Я пишу следующее также как общий пример из "GROUP BY" с дополнительным содержимым.
+-------------+
| MovieTicket |
++-----------++
| |
+-----+-----+ +--+---+
| Show | | User |
++---------++ +------+
| |
+---+---+ +--+--+
| Movie | | Day |
+-------+ +-----+
Вопрос: как суммировать MovieTicket (самый верхний объект), сгруппированный по Show (один связанный объект), отфильтрованный пользователем (другим связанным объектом) с отчетами о деталях из связанных объектов в зависимости от Show
и сортируйте эти результаты по некоторому агрегированному полю в сгруппированном (забронированное время последнего MovieTicket в группе)?
Ответ:
- Начните с самой верхней модели:
(MovieTicket.objects ...)
- Применить фильтры:
.filter(user=user)
- Важно группировать
pk
ближайших связанных моделей (по крайней мере те, которые не были заданы фильтром) - это только "Показать" (поскольку объект "Пользователь" все еще фильтруется для одного пользователя)
.values('show_id')
Даже если все остальные поля будут уникальными вместе (show__movie__name, show__day__date, show__time), лучше, чтобы оптимизатор движка базы данных группировал запрос с помощью show_id, потому что все эти другие поля зависят от show_id и не могут влиять на количество групп.
- Аннотировать необходимые функции агрегации:
.annotate(total_tickets=Count('show'), last_booking=Max('booked_at'))
- Добавить требуемые зависимые поля:
.values('show_id', 'show__movie__name', 'show__day__date', 'show__time')
- Сортировка, что необходимо:
.order_by('-last_booking')
(по убыванию от последней до самой старой)
Очень важно не выводить или сортировать любое поле самой верхней модели, не инкапсулируя его функцией агрегации (функции Min
и Max
хороши для выборки чего-либо из группы), поскольку будет добавлено любое поле, не инкапсулированное агрегацией к списку "group by", который разбивает предполагаемые группы.
Объединить его:
from django.db.models import Max
qs = (MovieTicket.objects
.filter(user=user)
.values('show_id', 'show__movie__name', 'show__day__date', 'show__time')
.annotate(total_tickets=Count('show'), last_booking=Max('booked_at'))
.order_by('-last_booking')
)
Запрос может быть легко преобразован в JSON, как показано zaphod100.10 в своем ответе, или непосредственно для людей, не заинтересованных в django- рамки отдыха таким образом:
from collections import OrderedDict
import json
print(json.dumps([
OrderedDict(
('show', x['show_id']),
('movie', x['show__movie__name']),
('time', x['show__time']), # add time formatting
('day': x['show__day__date']), # add date formatting
('total_tickets', x['total_tickets']),
# field 'last_booking' is unused
) for x in qs
]))
Проверить запрос:
>>> print(str(qs.query))
SELECT app_movieticket.show_id, app_movie.name, app_day.date, app_show.time,
COUNT(app_movieticket.show_id) AS total_tickets,
MAX(app_movieticket.booked_at) AS last_booking
FROM app_movieticket
INNER JOIN app_show ON (app_movieticket.show_id = app_show.id)
INNER JOIN app_movie ON (app_show.movie_id = app_movie.id)
INNER JOIN app_day ON (app_show.day_id = app_day.id)
WHERE app_movieticket.user_id = 23
GROUP BY app_movieticket.show_id, app_movie.name, app_day.date, app_show.time
ORDER BY last_booking DESC
Примечания:
-
График моделей похож на отношение ManyToMany, но MovieTickets являются отдельными объектами и, вероятно, содержат номера мест.
-
Было бы легко получить аналогичный отчет для большего числа пользователей по одному запросу. Поле "user_id" и имя будут добавлены в "values (...)".
-
Связанная модель Day не интуитивно понятна, но ясно, что есть поле date
и, надеюсь, также некоторые нетривиальные поля, возможно, важные для планирования передач в отношении таких событий, как праздники кино. Было бы полезно установить поле "дата" в качестве первичного ключа модели Day
и часто использовать отношения во многих запросах, подобных этому.
К сожалению, все необходимое можно найти в самых старых двух ответах (Todor и zaphod100.10), но они не были объединены вместе, чтобы получить непродуманный полный ответ, а не голосование по-любому, даже если у него много голосов.