Django REST框架-发布包含自然键的外键字段?

Django REST框架-发布包含自然键的外键字段?,django,django-models,django-rest-framework,Django,Django Models,Django Rest Framework,我最近开始使用Django REST框架(以及Django和Python——我是RTOS/嵌入式系统人员!)来实现RESTful Web API。我还没有遇到任何谷歌无法解决的问题,但这个问题已经困扰了我好几个小时了 我有一个嵌入式系统,它可以监听与一系列设备相关的事件——类似于打电话,为了简洁起见,我将在这里讨论这一点。一部电话有一个号码和一大堆与之相关的电话。通话有一个关联的电话(拨打电话的电话)和创建时间。当调用发生时,应该将其发布到API。我有一个嵌入式系统,它监听电话和他们的原始电话号

我最近开始使用Django REST框架(以及Django和Python——我是RTOS/嵌入式系统人员!)来实现RESTful Web API。我还没有遇到任何谷歌无法解决的问题,但这个问题已经困扰了我好几个小时了

我有一个嵌入式系统,它可以监听与一系列设备相关的事件——类似于打电话,为了简洁起见,我将在这里讨论这一点。一部电话有一个号码和一大堆与之相关的电话。通话有一个关联的电话(拨打电话的电话)和创建时间。当调用发生时,应该将其发布到API。我有一个嵌入式系统,它监听电话和他们的原始电话号码,并将它们提交给API。由于嵌入式系统知道电话号码,我希望它提交:
{“srcPhone”:12345678}
而不是
{“srcPhone”:http://host/phones/5“}
。这就避免了我的嵌入式系统需要知道每部手机的主键(或者每次它想提交电话时都要按号码获取手机)

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

型号.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',)
# 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,)
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')
视图.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',)
# 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,)
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')
序列化程序.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',)
# 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,)
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/
(在URL.py中配置为运行CallList视图)。这将在/calls/-'int'对象上提供一个AttributeError,该对象没有属性'startswith'。该异常出现在rest_framework/relations.py(第355行)中。如果有帮助的话,可以发布整个跟踪。阅读relations.py后,REST框架似乎不是按号码查找电话,而是像处理URL一样处理srcPhone属性。这通常是正确的,但我希望它通过自然键查找电话,而不是提供URL。我错过了什么


谢谢

您要查找的是
SlugRelatedField
。看

但是将srcPhone属性当作URL来处理

没错。您使用的是
HyperlinkedModelSerializer
,因此默认情况下,
srcPhone
键使用的是超链接关系

'int'对象没有属性“startswith”
您看到的异常是,它需要一个URL字符串,但接收的是一个整数。实际上,这应该会导致一个描述性的验证错误,所以我已经

如果改用类似以下内容的序列化程序:

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

    class Meta:
        model = Call
        fields = ('url', 'created', 'srcPhone')
然后,
'srcPhone'
键将使用关系目标上的
'number'
字段表示关系

我计划在不久的将来对关系文档进行更多的工作,希望这在将来会更加明显。

(不能将此作为评论发布,太长)

汤姆在上面的回答解决了这个问题


但是,我还希望有一个超链接字段返回到电话资源。SlugRelatedField允许我提交一个属于手机的整数字段,但在获取结果呼叫资源时,它也会序列化为整数。我确信这是预期的功能(将其从整数序列化到超链接似乎不是很优雅)。我找到的解决方案是向CallSerializer添加另一个字段:
src=serializers.HyperlinkedRelatedField(view\u name='phone-detail',source='srcPhone',blank=True,read\u only=True)
并将该字段添加到Meta类中。然后我只发布srcPhone(一个整数)并获得srcPhone plus src,这是一个指向电话资源的超链接。

谢谢Tom-现在我可以只提交一个整数srcPhone号码的电话。刚刚找到这个答案并解决了我的问题!很好explained@tom:附加的URL似乎不起作用。显示404错误页面。我不确定您是否需要该
blank=True
,但除此之外,是的,看起来不错。