我最近开始使用Django REST Framework(以及Django和Python - 我是一个RTOS /嵌入式系统人员!)来实现RESTful Web API。还没有任何问题,但谷歌无法解决这个问题,但现在这个问题让我感到难过了几个小时。
我有一个嵌入式系统,可以监听与各种设备相关的事件 - 类似于电话拨打电话,我将在这里简要讨论。电话有一个号码和一大堆与之相关的呼叫(它已经制作)。呼叫具有关联的电话(拨打电话的电话)和创建时间。发生调用时,应将其POST到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')
要测试,我创建一个号码为123456的电话。然后我将{“srcPhone”:123456}发送到http://host/calls/
(在urls.py中配置以运行CallList视图)。这给出了一个AttributeError at / calls / - 'int'对象没有属性'startswith'。 rest_framework / relations.py中出现异常(第355行)。如果它有用,可以发布整个跟踪。在阅读relations.py时,看起来REST框架没有按编号查找电话,而是处理srcPhone属性,就好像它是一个URL。这通常是正确的,但我希望它通过自然键查找电话,而不是提供URL。我在这里错过了什么?
谢谢!
答案 0 :(得分:13)
您正在寻找的是SlugRelatedField
。请参阅docs here。
但处理srcPhone属性就好像它是一个URL。
完全。您正在使用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'
字段来表示关系。
我计划在不久的某个时候为关系文档投入更多的工作,所以希望将来会更加明显。
答案 1 :(得分:1)
(不能将此评论作为评论过长)
汤姆上面的回答解决了这个问题。但是,我还希望将超链接字段返回到Phone资源。 SlugRelatedField允许我使用属于Phone的整数字段提交,但是当获取生成的Call资源时,它也会序列化为整数。我确定这是预期的功能(从一个整数序列化到一个超链接似乎不是很优雅)。我找到的解决方案是向CallSerializer添加另一个字段:src = serializers.HyperlinkedRelatedField(view_name='phone-detail',source='srcPhone',blank=True,read_only=True)
并将该字段添加到Meta类。然后我只发布srcPhone(一个整数)和GET srcPhone加上src,它是一个指向Phone资源的超链接。