2013-01-16 63 views
12

为了实现REST风格的Web API,我最近开始使用Django REST框架(以及Django和Python--我是一个RTOS /嵌入式系统人员!)。目前还没有任何问题无法用Google解决,但是这个问题让我现在陷入了几个小时的困境。Django REST框架 - 发布包含自然键的外键字段?

我有一个嵌入式系统,用于监听与一系列设备相关的事件 - 类似于拨打电话的电话,这是我在这里为简洁起见而讨论的。一个电话有一个号码和一大堆呼叫(它已经)与之相关联。呼叫具有关联的电话(发起呼叫的电话)和创建时间。发生呼叫时,应将其发送到API。我有一个嵌入式系统,用于监听呼叫及其始发电话号码,并将它们提交给API。由于嵌入式系统知道电话号码,我希望它提交:{"srcPhone":12345678}而不是{"srcPhone":"http://host/phones/5"}。这样可以避免我的嵌入式系统需要知道每个电话的主键(或每次拨打电话时都按编号获取电话)。

Google和Django文档建议我可以用自然键实现这一点。我尝试如下:

models.py

from django.db import models 
from datetime import datetime 
from pytz import timezone 
import pytz 
from django.contrib.auth.models import User 

# Create your models here. 
def zuluTimeNow(): 
    return datetime.now(pytz.utc) 


class PhoneManager(models.Manager): 
    def get_by_natural_key(self, number): 
     return self.get(number=number) 


class Phone(models.Model): 
    objects  = PhoneManager() 
    number  = models.IntegerField(unique=True) 

    #def natural_key(self): 
    # return self.number 

    class Meta: 
     ordering = ('number',) 


class Call(models.Model): 
    created = models.DateTimeField(default=zuluTimeNow, blank=True) 
    srcPhone = models.ForeignKey('Phone', related_name='calls') 

    class Meta: 
     ordering = ('-created',) 

views.py

# Create your views here. 
from radioApiApp.models import Call, Phone 
from radioApiApp.serializers import CallSerializer, PhoneSerializer 
from rest_framework import generics, permissions, renderers 
from rest_framework.reverse import reverse 
from rest_framework.response import Response 
from rest_framework.decorators import api_view 

@api_view(('GET',)) 
def api_root(request, format=None): 
    return Response({ 
     'phones': reverse('phone-list', request=request, format=format), 
     'calls': reverse('call-list', request=request, format=format), 
    }) 


class CallList(generics.ListCreateAPIView): 
    model = Call 
    serializer_class = CallSerializer 
    permission_classes = (permissions.AllowAny,) 

class CallDetail(generics.RetrieveDestroyAPIView): 
    model = Call 
    serializer_class = CallSerializer 
    permission_classes = (permissions.AllowAny,) 

class PhoneList(generics.ListCreateAPIView): 
    model = Phone 
    serializer_class = PhoneSerializer 
    permission_classes = (permissions.AllowAny,) 

class PhoneDetail(generics.RetrieveDestroyAPIView): 
    model = Phone 
    serializer_class = PhoneSerializer 
    permission_classes = (permissions.AllowAny,) 

serializers.py

from django.forms import widgets 
from rest_framework import serializers 
from radioApiApp import models 
from radioApiApp.models import Call, Phone 

class CallSerializer(serializers.HyperlinkedModelSerializer): 
    class Meta: 
     model = Call 
     fields = ('url', 'created', 'srcPhone') 

class PhoneSerializer(serializers.HyperlinkedModelSerializer): 
    calls = serializers.ManyHyperlinkedRelatedField(view_name='call-detail') 
    class Meta: 
     model = Phone 
     fields = ('url', 'number', 'calls') 

ŧ o测试,我创建了一个号码为123456的电话。然后我POST {“srcPhone”:123456}到http://host/calls/(在urls.py中配置它来运行CallList视图)。这在/ calls/- 'int'对象的AttributeError中没有属性'startswith'。 rest_framework/relations.py中出现异常(第355行)。可以发布整个跟踪,如果它会有所帮助。在阅读关系.py时,它看起来像REST框架没有按数字查找电话,而是像处理URL一样处理srcPhone属性。这通常是真实的,但我希望它通过自然键来查找电话,而不是提供URL。我在这里错过了什么?

谢谢!

回答

13

您要找的是SlugRelatedField。见docs here

但是像处理URL一样处理srcPhone属性。

没错。您正在使用HyperlinkedModelSerializer,因此srcPhone键默认使用超链接关系。

你看到的'int' object has no attribute 'startswith'异常是因为它期待一个URL字符串,但接收一个整数。真的这应该导致一个描述性验证错误,所以我created a ticket for that

如果改为使用串行是这样的:

class CallSerializer(serializers.HyperlinkedModelSerializer): 
    srcPhone = serializers.SlugRelatedField(slug_field='number') 

    class Meta: 
     model = Call 
     fields = ('url', 'created', 'srcPhone') 

然后'srcPhone'键将使用上的关系的目标'number'领域,而不是代表的关系。

我打算在不久的将来在关系文档中增加更多的工作,所以希望这在未来会更加明显。

+0

谢谢你汤姆 - 现在我只能提交一个整数srcPhone号码的呼叫。 – EwanC

+0

刚刚找到了这个答案,并解决了我的问题!非常好解释 – zeroliu

1

上面解决了这个问题(不能张贴此作为一个评论,太长)

汤姆的回答。

但是,我也想要超链接字段回到电话资源。 SlugRelatedField允许我使用属于电话的整数字段进行提交,但是当获得生成的呼叫资源时,它也会作为整数串行化。我确信这是一个预期的功能(将它从一个整数串行化到超链接似乎并不是很优雅)。我找到的解决方案是将另一个字段添加到CallSerializer:src = serializers.HyperlinkedRelatedField(view_name='phone-detail',source='srcPhone',blank=True,read_only=True)并将该字段添加到Meta类。然后,我只POST srcPhone(一个整数)和GET srcPhone plus src,这是一个超级链接到电话资源。

+0

我不确定你需要那个'blank = True',但是否则,看起来不错。 –

相关问题