Django REST框架按字段分组并添加额外内容

Django REST框架按字段分组并添加额外内容,django,django-rest-framework,Django,Django Rest Framework,我有一个订票模型 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 Movi

我有一个订票模型

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,并根据它的
显示
字段对它们进行分组,并按最近预定的时间订购它们。并使用Django REST框架使用
json
数据进行响应,如下所示:

[
    {
        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}]>
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
]))
>>MovieTicket.objects.filter(用户=23).订购人('-booked_at').值('show').注释(总票数=Count('show'))

但这并不是根据节目进行分组。另外,我如何添加其他相关字段(例如,
show\uu movie\uu name
show\uu day\uu date
show\uu time

您必须按节目分组,然后计算电影票总数

MovieTicket.objects.filter(user=23).values('show').annotate(total_tickets=Count('show')).values('show', 'total_tickets', 'show__movie__name', 'show__time', 'show__day__date'))
将此序列化器类用于上述查询集。它将提供所需的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()
由于按节目分组时,信息丢失,因此无法按预定地点订购。如果我们在group by预订,则会在unique booked_上发生并显示ID,这就是票数为1的原因。没有订单,你会得到正确的计数

编辑:

使用此查询:

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中找到总票数。要计算唯一演出ID的总门票数量,请执行以下操作:

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()

上述相同的序列化程序可用于这些对象

我想用MovieTicket的用户字段过滤MovieTicket并将它们分组 根据其展示区域,并在最近预定的时间内订购

queryset
将为您提供您想要的:

tickets = (MovieTicket.objects
            .filter(user=request.user)
            .values('show')
            .annotate(last_booking=Max('booked_at'))
            .order_by('-last_booking')
)
并使用Django rest框架使用json数据进行响应,如下所示: [ { 节目:4,, 电影:《狮子王》, 时间:"晚上七时", 日期:“2017年4月23日”, 门票总数=2张 }, { 节目:7,, 电影:《消失的女孩》, 时间:"下午二时三十分", 日期:“2017年4月23日”, 总票数=1 } ]

这个json数据与您描述的查询不同。您可以通过扩展注释并将
show\u movie\u name
添加到
.values
子句中来添加
total\u tickets
:这会将分组更改为show+movie\u name,但由于show只有一个movie\u name,因此没有关系

但是,您不能添加
show\uuu day\uu date
show\uu 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')
)
你可以试试这个

Show.objects.filter(movieticket_sets__user=23).values('id').annotate(total_tickets=Count('movieticket_set__user')).values('movie__name', 'time', 'day').distinct()


我在数据库模型的图形上更一般地解释它。它可以应用于任何带有额外内容的“分组依据”

          +-------------------------+
          | MovieTicket (booked_at) |
          +-----+--------------+----+
                |              |
      +---------+--------+  +--+---+
      |    Show (time)   |  | User |
      ++----------------++  +------+
       |                |
+------+-------+  +-----+------+
| Movie (name) |  | Day (date) |
+--------------+  +------------+
问题是:如何总结MovieTicket(最顶端的对象),按Show(一个相关对象)分组,并按用户(其他相关对象)筛选,报告来自一些相关深层对象(电影和日期)的详细信息,并按组从最顶端模型聚合的某个字段对这些结果进行排序(截至集团最近一部电影的预定时间):

答案由更一般的步骤解释:

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')
      )
  • 从最顶层的模型开始:
    (MovieTicket.objects…)
  • 应用过滤器:
    .filter(用户=用户)
  • 重要的是,根据最近的相关模型(至少是那些没有被过滤器设置为常量的模型)的
    pk
    进行分组-这只是“显示”(因为“用户”对象仍然被过滤到一个用户)
    .values('show_id')

    即使所有其他字段在一起都是唯一的(show\u movie\u name、show\u day\u date、show\u time),数据库引擎优化器最好按show\u id对查询进行分组,因为所有其他字段都依赖于show\u id,并且不会影响组数
  • 注释必要的聚合函数:
    .annotation(总票数=计数('show'),最后一次订票=最大值('booked'at'))
  • 添加必需的相关字段:
    值('show\u id'、'show\u movie\u name'、'show\u day\u date'、'show\u time')
  • 按需要排序:
    .order\u by('-最后一次预订)
    (从最新到最旧的降序)
    如果不使用聚合函数对最顶层模型的任何字段进行封装,则不输出或对其进行排序是非常重要的。(
    Min
    Max
    函数适用于从组中取样。每个未使用聚合函数封装的字段都将添加到“分组依据”中列出,这将打破预定的群体。可以逐步为朋友预订同一节目的更多门票,但应一起计算,并在最新预订中报告。)
把它放在一起:

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')
      )
queryset可以很容易地转换为JSON如何在他的答案中演示,或者直接用于对django rest框架不感兴趣的人:

>>> 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}]>
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
]))

验证查询:

打印(str(qs.query))
选择app\u movieticket.show\u id、app\u movie.name、app\u day.date、app\u show.time、,
将(app\u movieticket.show\u id)计入总票数,
MAX(app_movieticket.booked_at)作为最后一次预订
来自app_movieticket
内部加入app\u show ON(app\u movieticket.show\u id=app\u show.id)
内部加入app\u movie ON(app\u show.movie\u id=app\u movie.id)
内部加入app_day ON(app_show.day_id=app_day.id)
其中app_movieticket.user_id=23
按app_movieticket.show_id、app_movie.name、app_day.date、app_show.time分组
按上次预订说明订购
注意事项:

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')
      )
  • 模型图类似于ManyToMa