18

我正在尝试实现一个嵌套资源,其中一个字段取决于其父资源的值。

假设我们正在为一家公司构建一个系统,该系统提供有关其客户的信息以及公司销售人员的销售数据。所以我们有两个模型,CustomerRep。代表可以向多个客户销售产品。

返回所有客户的 URL:/api/1.0/customers/

特定客户的 URL:/api/1.0/customers/123/

特定销售代表的客户特定信息的 URL:/api/1.0/customers/123/rep/9/

请注意,代表 URL 包含客户 ID 以及代表 ID。

我希望客户 URL 返回一个嵌套资源,其中包含有关代表的摘要信息,以及指向该代表的完整客户特定信息的超链接。这是所有客户的 URL 的输出:

[
    {
        "id": 100, 
        "customer_name": "DolManSaxLil",
        "rep": {
                "id": 4,
                "annual_sales": 1500.01,
                "name": "Fred",
                "url": "http://localhost:8000/api/1.0/customer/100/rep/4/"
               }
    },
    {
        "id": 200, 
        "customer_name": "Hotblack",
        "rep": {
                "id": 4,
                "annual_sales": 10500.42,
                "name": "Fred",
                "url": "http://localhost:8000/api/1.0/customer/200/rep/4/"
               }
    }
]

为了实现这一点,我们定义了两个序列化器:

class CustomerSummarySerializer(serializers.HyperlinkedModelSerializer):
    id = ...
    name = ...
    rep = RepSummarySerializer(read_only=True)

class RepSummarySerializer(serializers.HyperlinkedModelSerializer):
    id = ...
    annual_sales = ...
    name = ....
    url = serializers.SerializerMethodField('get_rep_url')

我面临的问题是我无法弄清楚如何从函数访问当前的 customer.id RepSummarySerializer.get_rep_url。当客户在以下位置时,可以在详细视图中self.parent.obj

def get_rep_url(self, obj):
    customer_id = self.parent.obj.id
    url = reverse('api_customer_rep', 
              kwargs={'customer_id': customer_id,
                      'rep_id': obj.id},
                      request=serializer.context.get('request'))
    return url

但是,在列表视图中,self.parent.obj是 Customer 对象的 QuerySet,我看不到任何识别当前客户的方法。有没有办法做到这一点?我错过了什么明显的东西吗?

4

2 回答 2

19

清晰的时刻:解决方案是使用 aSerializerMethodField实例化并在上下文中RepSummarySerializer传递:customer_id

class CustomerSummarySerializer(serializers.HyperlinkedModelSerializer):
    id = ...
    name = ...
    rep = serializers.SerializerMethodField('get_rep')


    def get_rep(self, obj):
        rep = obj.rep
        serializer_context = {'request': self.context.get('request'),
                              'customer_id': obj.id}
        serializer = RepSummarySerializer(rep, context=serializer_context)
        return serializer.data

现在customer_id可以RepSummarySerializer.get_rep_url像这样访问:

def get_rep_url(self, obj):
    customer_id = self.context.get('customer_id')
    ...

不知道为什么我三个小时前没有想到这个。

于 2013-08-30T18:50:42.227 回答
2

除了接受的答案之外,如果您使用视图集并希望您的子资源仅是一个集合(由父文档过滤),您还可以使用@detail_route装饰器,如文档中所述

from rest_framework import viewsets
from rest_framework.decorators import detail_route
from rest_framework.response import Response

class CustomerViewSet(viewsets.ModelViewSet):
    queryset = Customer.objects.all()
    serializer_class = CustomerSummarySerializer

    ...

    @detail_route(methods=['get'])
    def rep(self, request, pk=None):
        customer = self.get_object()
        queryset = customer.pk.all()
        instances = self.filter_queryset(queryset)
        serializer = RepSummarySerializer(instances,
                context={'request': request}, many=True)
        return Response(serializer.data)

现在您可以查询,您将获得指定客户/customers/123/rep/的所有实例的列表。Rep

它可能无法完全解决您的需求,但对于许多不需要完整嵌套资源的人来说,这实际上就足够了。

于 2015-02-24T09:00:50.613 回答