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

Posted

技术标签:

【中文标题】Django REST Framework - 发布包含自然键的外键字段?【英文标题】:Django REST Framework - POSTing foreign key field containing natural key? 【发布时间】:2012-12-31 07:32:22 【问题描述】:

我最近开始使用 Django REST 框架(以及 Django 和 Python - 我是 RTOS/嵌入式系统人员!)来实现 RESTful Web API。还没有遇到任何无法通过 Google 解决的问题,但这个问题让我难倒了几个小时。

我有一个嵌入式系统,它侦听与一系列设备相关的事件 - 类似于电话拨打电话,为了简洁起见,我将在这里讨论。电话有一个号码和大量与之相关的呼叫(它已拨打)。呼叫具有关联的电话(发出呼叫的电话)和创建时间。发生调用时,应将其发布到 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 视图)。这会在 /calls/ 处产生 AttributeError -“int”对象没有属性“startswith”。异常发生在 rest_framework/relations.py(第 355 行)中。如果有帮助,可以发布整个跟踪。在阅读了relationship.py 后,看起来REST 框架并没有按号码查找电话,而是将srcPhone 属性当作一个URL 来处理。这通常是正确的,但我希望它通过自然键查找电话,而不是提供 URL。我在这里错过了什么?

谢谢!

【问题讨论】:

【参考方案1】:

(不能作为评论发表,太长了)

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

但是,我还希望有一个超链接字段返回到电话资源。 SlugRelatedField 允许我使用属于电话的整数字段提交,但是在获取结果呼叫资源时,它也会序列化为整数。我确定这是预期的功能(让它从整数序列化似乎不是很优雅,而是从超链接序列化)。我找到的解决方案是向 CallSerializer 添加另一个字段:src = serializers.HyperlinkedRelatedField(view_name='phone-detail',source='srcPhone',blank=True,read_only=True) 并将该字段添加到 Meta 类。然后我只 POST srcPhone(一个整数)和 GET srcPhone 加上 src,这是一个到 Phone 资源的超链接。

【讨论】:

我不确定您是否需要 blank=True,但除此之外,是的,看起来不错。【参考方案2】:

您正在寻找的是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' 字段来表示关系。

我计划在不久的某个时候对关系文档进行更多工作,因此希望这在未来会更加明显。

【讨论】:

感谢 Tom - 现在我只能使用整数 srcPhone 号码提交呼叫。 刚刚找到这个答案,也解决了我的问题!很好的解释 @tom:附加的 URL 似乎不起作用。显示 404 错误页面。

以上是关于Django REST Framework - 发布包含自然键的外键字段?的主要内容,如果未能解决你的问题,请参考以下文章

Django Rest Framework 和 django Rest Framework simplejwt 两因素身份验证

怎么安装django rest framework

django rest framework中文介绍

17-Django-Django REST framework-REST framework及RESTful简介

为啥 django-rest-framework 不显示 OneToOneField 数据 - django

Django:rest framework之分页(Pagination)