Django Rest Framework:如何发布新的音轨排序的示例

时间:2017-11-23 06:01:22

标签: python django rest django-rest-framework

我正在使用Django Rest Framework,我的情况类似于此处文档中使用的Album and Tracks示例:http://www.django-rest-framework.org/api-guide/relations/#api-reference

我想创建一个允许我发布此JSON数据的端点

[{"id":1},{"id":2},{"id":3},{"id":4},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":75},{"id":76},{"id":77},{"id":78}]

这样曲目将在其所属的专辑中进行相应的重新排序。

JSON数据中的ID指的是特定相册中曲目的主键。

我用来存储订单的字段为order,唯一索引为unique_together = ('album', 'order')

我可以编写自己的自定义端点来满足这一要求,但是想知道我是否可以重用DRF已经拥有的任何现有代码来实现这一点。

我看过https://github.com/miki725/django-rest-framework-bulk但是我正在使用Django Rest 3.6.3 Django 1.10,python 3所以它似乎已经过时了。

我想更新一个特定相册中曲目的新顺序,给出特定相册的曲目ID列表,如上面的JSON数据所示。< / p>

2 个答案:

答案 0 :(得分:2)

首先,我建议删除这个独特的约束,在我看来,它会让你的生活方式变得更加困难。

说真的,我不认为对订单设置一个独特的约束是一个好主意,删除它,确保验证理智的值,并级联你的订单,继续...

如果你想更新曲目的顺序,你需要在订单周围玩耍,这是一些python伪代码:

#  original ordering is {"1":1, "2":2, "3":3}
request_data = {"3":1, "2":3, "1":2}

# I need to get a track to filter on the album
example_track = Track.objects.get(pk=request_data.keys()[0])
the_album = example_track.album

#  Get the largest ordering value, so we can update these to avoid collisions which throw duplicate value errors from the database
tmp_order = max(Track.objects.filter(album=album).values_list('order', flat=True)) + 1

# Reorder all your tracks using the tmp_order, tmp_order is big enough to never collide with other orderings...
for track in Track.objects.filter(pk__in=list(request_data.keys()):
    track.order = tmp_order
    track.save()
    tmp_order = tmp_order + 1

# Update the tracks again to set the final ordering
for track_id, order_num in request_data:
    track = Track.objects.get(pk=track_id)
    track.order = order_num
    track.save()

此解决方案的一个问题是,如果request_data不包含相册中的所有曲目,它仍然可能会发生碰撞和错误!

#  original ordering is {"1":1, "2":2, "3":3, "4": 4}
request_data = {"3":1, "2":3, "1":4}

由于Track#4未包含在请求数据中,并且没有其他值可用于排序,因此除非您愿意更改Track#4的顺序,否则Track#1永远不会被分配4。

此示例部分原因是我建议您不要在曲目上强制执行唯一排序。您可以在应用程序逻辑中使用验证,而不是在数据库上强制执行完整性,并且在这种情况下,您将避免使用数据库完整性约束的大量pita。如果两个轨道具有相同的顺序会发生什么?他们应该通过其他一些领域(赛道名称)进行排序,这不是一个糟糕的结果并且不会破坏任何东西,相当于使用错误顺序的两个项目!这将使您更容易编写可维护的应用程序。

答案 1 :(得分:1)

  1. 我对从前端到端点的有效负载进行了(微不足道的)更改。
  2. 我使用的是Django Rest Framework,但没有什么特别的东西我可以从DRF重用。
  3. 我使用Django批量更新https://github.com/aykut/django-bulk-update来帮助进行批量更新(这实际上非常有用)
  4. 我选择使用patch作为方法,除了受影响的行数以外,如果成功则不返回任何内容
  5. 我删除了Track模型中的unique_together约束。 (这非常重要)
  6. 更改有效负载

    而不是

    [{"id":1},{"id":2},{"id":3},{"id":4},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":75},{"id":76},{"id":77},{"id":78}]
    

    我做了

    {"children" :[{"id":1},{"id":2},{"id":3},{"id":4},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":75},{"id":76},{"id":77},{"id":78}]}
    

    DRF端点

    在urls.py中

    url(r'^(?i)api/v1.0/album/(?P<pk>[0-9]+)/update_children_order$',
            views.TracksList.as_view(),
            name='view-tracks'),
    
    在views.py中

    from django_bulk_update.helper import bulk_update
    from rest_framework.response import Response
    import json
    from rest_framework.views import APIView
    
     class TracksReorder(APIView):
    
        def patch(self, request, pk=None):
            """
            Update the order of the tracks by the parent album id
            request.data is in the expected structure:
            track pk is in data.children.{n}.id
            """
            # because the data is expected to be nested dictionary
            decoded = request.body.decode('utf8')
            data = json.loads(decoded)
    
            sorted_dict = {}
            for key, value in enumerate(data['children']):
                sorted_dict[value['id']] = key
    
            # sorted dict uses track pk as key and new order as value
            tracks = Track.objects.filter(album_id=pk)
            for track in tracks:
                track.order = sorted_dict[track.id]
            affected_rows = bulk_update(tracks, update_fields=['order'])
            if affected_rows:
                return Response({'affected_rows': affected_rows})
            return Response({}, status.HTTP_204_NO_CONTENT)