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), но они не были объединены вместе, чтобы получить непродуманный полный ответ, а не голосование по-любому, даже если у него много голосов.