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

时间:2017-04-23 17:31:05

标签: 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 MovieTicket(models.Model):
    show = models.ForeignKey(Show)
    user = models.ForeignKey(User)
    booked_at = models.DateTimeField(default=timezone.now)

我想用其user字段过滤MovieTicket,并根据其show字段对它们进行分组,并按照最近预订的时间对它们进行排序。并使用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}]>

但根据节目不分组。另外,我如何添加其他相关字段(例如show__movie__nameshow__day__dateshow__time

4 个答案:

答案 0 :(得分:2)

我正在绘制数据库模型,让人难以忘怀。我还将以下内容写成“GROUP BY”的一般示例,并附加了额外的内容。

        +-------------+
        | MovieTicket |
        ++-----------++
         |           |
   +-----+-----+  +--+---+
   |   Show    |  | User |
   ++---------++  +------+
    |         |
+---+---+  +--+--+
| Movie |  | Day |
+-------+  +-----+

问题是:如何汇总按用户(其他相关对象)过滤的Show(一个相关对象)分组的MovieTicket(最顶层对象),并根据{{1}报告来自相关对象的视频并按分组中的某些聚合字段(按组中最近的MovieTicket的预订时间)对这些结果进行排序?

<强>答案:

  • 从最顶层的模型开始:
    Show
  • 应用过滤器:
    (MovieTicket.objects ...)
  • 重要的是按最近的相关模型.filter(user=user)进行分组(至少那些过滤器不能保持不变的模型) - 它只是“显示”(因为“用户”对象仍被过滤为一个用户)
    pk
    即使所有其他字段在一起是唯一的(show__movie__name,show__day__date,show__time),数据库引擎优化器最好通过show_id对查询进行分组,因为所有这些其他字段都依赖于show_id,并且不会影响组的数量。
  • 注释必要的聚合函数:
    .values('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

查询集可以很容易地转换为JSON 如何在他的答案中演示zaphod100.10,或者直接对于那些对django-rest框架不感兴趣的人:

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

验证查询:

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

备注:

  • 模型图类似于ManyToMany关系,但MovieTickets是单个对象,可能包含座位号。

  • 通过一个查询为更多用户提供类似的报告很容易。字段'user_id'和名称将添加到“values(...)”。

  • 相关的模型日并不直观,但很明显,它有一个字段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 ,并且希望也有一些非平凡的字段,对于像电影假期这样的事件安排节目可能很重要。将字段'date'设置为date模型的主键并在许多查询中经常备用关系会很有用。

遗憾的是,所有必要的一切都可以在最早的两个答案中找到(Todor和zaphod100.10),但它们并没有结合在一起得到一个完整的答案,而不是由任何人投票,甚至这个问题有很多选票。

答案 1 :(得分: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()

订单_by scheduled_at是不可能的,因为当我们按show分组时,该信息会丢失。如果我们按scheduled_at group by订购将发生在唯一的registered_at次和show ids上,这就是票数计数的原因1.如果没有order_by,你将获得正确的计数。

编辑:

使用此查询:

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中找到总票数。计算唯一show id的total_tickets计数:

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 :(得分:1)

  

我想用其用户字段过滤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,              电影:“狮子王”,              时间:“07:00 pm”,              日:“2017年4月23日”,              total_tickets = 2          },          {              显示:7,              电影:“走了的女孩”,              时间:“02:30 pm”,              日:“2017年4月23日”,              total_tickets = 1          }      ]

这个json数据与你描述的查询不一样。您可以通过将注释和total_tickets扩展到show__movie__name子句来添加.values:这会将分组更改为show + movie_name,但由于show只有一个movie_name,因此无关紧要。

但是,您无法添加show__day__dateshow__time,因为一个节目有多个日期时间,那么您想从群组中获得哪一个?例如,您可以获取最大daytime,但这并不能保证您在当天+时间会有节目,因为这些是不同的字段,彼此不相关。所以最后的尝试可能如下:

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 :(得分:1)

你可以试试这个。

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

OR

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