【问题标题】:How to implement a hierarchy of resources (eg. /parents/<id>/children) in Django REST Framework如何在 Django REST Framework 中实现资源层次结构(例如 /parents/<id>/children)
【发布时间】:2013-06-24 15:23:27
【问题描述】:

我在 Django REST Framework 网站上的教程中找不到任何关于如何实现这一点的信息,我也没有设法在文档中找到它,但我确信它就在某个地方。

我希望issues 成为父资源,pages 成为子资源,以便/issues/1/pages 返回所有具有issue_id 的页面。

有没有使用基于通用类的视图实现此目的的好方法?

这是我目前所拥有的。

restAPI/urls.py:

from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from restAPI import views

urlpatterns = patterns('',
    url(r'^issues/$', views.IssueList.as_view()),
    url(r'^issues/(?P<pk>[0-9]+)/$', views.IssueDetail.as_view()),


    url(r'^issues/(?P<issue_id>[0-9]+)/pages/$', views.PageList.as_view()),    
    url(r'^pages/(?P<pk>[0-9]+)/$', views.PageDetail.as_view()),
)

urlpatterns = format_suffix_patterns(urlpatterns)

restAPI/models.py:

from django.db import models

class Issue(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    revision = models.IntegerField(default = 1)
    issue_date = models.DateTimeField(auto_now_add=True)
    issue_image_url = models.CharField(max_length=100)

class Page(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    page_number = models.IntegerField()
    standard_page_url = models.CharField(max_length=100, default='')
    large_page_url = models.CharField(max_length=100, default='')
    thumbnail_url = models.CharField(max_length=100, default='')

    issue = models.ForeignKey(Issue, related_name="pages")

    class Meta:
        ordering = ('page_number',)

restAPI/serializers.py:

from rest_framework import serializers
from restAPI.models import Page, Issue

class IssueSerializer(serializers.ModelSerializer):
    class Meta:
        model = Issue
        fields = ('id', 'created', 'revision', 'issue_date', 'issue_image_url')

class PageSerializer(serializers.ModelSerializer):       
    class Meta:
        model = Page
        fields = ('id', 'created', 'page_number', 'standard_page_url', 'large_page_url', 'thumbnail_url')

restAPI/views.py:

from restAPI.models import Page, Issue
from restAPI.serializers import PageSerializer, IssueSerializer
from rest_framework import mixins
from rest_framework import generics

class IssueList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Issue.objects.all()
    serializer_class = IssueSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class IssueDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Issue.objects.all()
    serializer_class = IssueSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)  

class PageList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Page.objects.all()
    serializer_class = PageSerializer

    def get(self, request, *args, **kwargs):
        print kwargs
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class PageDetail(mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   generics.GenericAPIView):
    queryset = Page.objects.all()
    serializer_class = PageSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

如何实现issuespages 之间的这种关系?

【问题讨论】:

  • 我将def get_queryset(self): issue_id = self.kwargs['issue_id'] return Page.objects.filter(issue_id = issue_id) 添加到PageList,现在GET 适用于issue/&lt;id&gt;/pages。现在我只需要弄清楚如何发布。
  • 我将def pre_save(self, obj): obj.issue_id = self.kwargs['issue_id'] 添加到PageList,现在POST 也可以使用了。从不存在的问题中查询页面会返回一个空结果,而不是 404 not found。如果有人知道更好的方法,我很想听听。

标签: django django-rest-framework


【解决方案1】:

这是我使用来自 Rest-Framework 2.3 版的新 ViewSetsRouters 实现这一目标的方法:

views.py:

from rest_framework import viewsets
from rest_framework.response import Response
from models import Order, OrderLine
from serializers import OrderSerializer, OrderLineSerializer

class OrderViewSet(viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

    @link()
    def lines(self, request, pk=None):
        queryset = OrderLine.objects.filter(order__pk=pk)
        serializer = OrderLineSerializer(queryset,
                           context={'request':request},
                           many=True)
        return Response(serializer.data)

class OrderLineViewSet(viewsets.ModelViewSet):
    queryset = OrderLine.objects.all()
    serializer_class = OrderLineSerializer

serializers.py

from rest_framework import serializers
from models import Order, OrderLine

class OrderSerializer(serializers.HyperlinkedModelSerializer):
    lines = serializers.HyperlinkedIdentityField(view_name='order-lines')

    class Meta:
        model = Order


class OrderLineSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = OrderLine

urls.py

from views import OrderViewSet, OrderLineViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'order', OrderViewSet)
router.register(r'orderline', OrderLineViewSet)
urlpatterns = router.urls

现在,'order/id/lines' 将返回与由该 id 标识的订单有关系的序列化订单行列表。

当您向路由器注册视图时,任何用@link 或@action 装饰的ViewSet 方法都将获得一个URL。

【讨论】:

  • 感谢干净的示例。但是,我还希望子资源的 POST 位于 parents//children 下,并且我希望孩子只能在其父母下访问,因此 children/ 甚至不应该是有效路径。我想可以通过使用@action 修饰的方法来修复发布,但是与基于类的视图相比,它似乎没有太大的优势。不过,我真的很喜欢路由器的想法。
  • 在进一步探索之后,我认为使用 ViewSets 和 Routers 是方便和容易的,但仍然需要大量的自定义才能让它们做一些类似于父子关系的事情。我将在另一个答案中发布另一种更明确的方式。
  • 在使用这种分层资源技术和 ViewSet 和 @link 时,您是否找到了一种方法来更改在跟随 @link 时显示在 Browseable API 顶部的文档字符串?实际上,它似乎显示了父级 (Order) 而不是子级 (OrderLine) 的帮助。
  • @link 装饰器从何而来?示例中没有导入。
【解决方案2】:

这是我这样做的另一种方式:

views.py

from models import Customer, Order
from serializers import CustomerSerializer, OrderSerializer

from rest_framework import generics

class CustomerList(generics.ListCreateAPIView):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer

class CustomerDetail(generics.RetrieveUpdateDestroyAPIView)
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer

class OrdersByCustomer(generics.ListCreateAPIView):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

    def get_queryset(self):
        customer_pk = self.kwargs['customer_pk']
        return self.queryset.filter(customer__pk=customer_pk)

    def pre_save(self, obj):
        obj.customer_id = self.kwargs['customer_pk'] 

class OrderDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

serializers.py

from models import Customer, Order

from rest_framework import serializers
from rest_framework.reverse import reverse

class OrderSerializer(serializers.HyperlinkedModelSerializer)

    class Meta:
        model = Order

class CustomerSerializer(serializers.HyperlinkedModelSerializer)

    orders = serializers.SerializerMethodField('get_customer_orders')

    def get_customer_orders(self, obj):
        return reverse('ordersbycustomer-list', 
               args=[obj.pk], request=self.context['request'])

    class Meta:
        model = Customer

urls.py

from django.conf.urls import patterns, include, url
from views import OrdersByCustomer, CustomerDetail, CustomerList

urlpatterns = patterns("",
    url(r'^customers/(?P<customer_pk>.+)/orders/$', OrdersByCustomer.as_view(), name='ordersbycustomer-list'),
    url(r'^customers/(?P<pk>.+)/$', CustomerDetail.as_view(), name='customer-detail'),
    url(r'^customers/$', CustomerList.as_view(), name='customer-list'),
    )

与 Viewsets/Routers 相比,涉及的代码更多,但这使您可以更好地控制正在发生的事情。

在这里,我选择仅将订单公开为客户的子级。由于它们是分开的,因此您可以对列表和详细信息使用不同的序列化程序类。

【讨论】:

  • 如果您将 pre_save 添加到 OrdersByCustomer 中,类似于我在原始问题的答案中所写的方式,我会将其设为已接受的答案。
【解决方案3】:

我添加 def get_queryset(self): issue_id = self.kwargs['issue_id'] return Page.objects.filter(issue_id = issue_id) 到 PageList 并且现在 GET 适用于 issue/ /页。现在我只需要弄清楚如何发布。

我将 def pre_save(self, obj): obj.issue_id = self.kwargs['issue_id'] 添加到 PageList 并且现在 POST 也可以使用。从不存在的问题中查询页面会返回空结果,而不是 404 not found。如果有人知道更好的方法来做到这一点,我很想知道。

如果您的方法 get_queryset(self) 返回一个空列表而不是 404 NOT FOUND,我建议使用 django 的快捷函数 get_list_or_404。 该方法可能如下所示:

from django.shortcuts import get_list_or_404

def get_queryset(self):
    filter = {}
    filter['issue_id'] = self.kwargs['issue_id']
    return get_list_or_404(self.queryset, **filter)

我知道这是一篇旧帖子,但也许这可以帮助其他遇到相同或类似问题的人。

【讨论】:

    猜你喜欢
    • 2013-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多