Подтвердить что ты не робот

Django REST Framework - поле POSTing внешнего ключа, содержащее естественный ключ?

Недавно я начал использовать Django REST Framework (и 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. Затем я отправлю POST { "srcPhone": 123456} на http://host/calls/ (который настроен в urls.py для запуска представления CallList). Это дает атрибут AttributeError at/calls/- 'int' не имеет атрибута 'startswith'. Исключение происходит в rest_framework/relations.py(строка 355). Можно опубликовать весь трассу, если это будет полезно. После чтения отношений .py, похоже, что REST Framework не ищет телефоны по номеру, а обрабатывает атрибут srcPhone, как если бы это был URL. Это, как правило, верно, но я хочу, чтобы он просматривал телефоны по естественному ключу, а не предоставлял URL-адрес. Что я пропустил здесь?

Спасибо!

4b9b3361

Ответ 1

То, что вы ищете, это SlugRelatedField. Смотрите docs здесь.

но обработать атрибут srcPhone, как если бы это был URL.

Совершенно верно. Вы используете HyperlinkedModelSerializer, поэтому ключ srcPhone использует отношение гиперссылки по умолчанию.

Исключение 'int' object has no attribute 'startswith', которое вы видите, связано с тем, что оно ожидает строку URL, но получает целое число. На самом деле это должно привести к описательной ошибке проверки, поэтому я создал билет для этого.

Если вы используете сериализатор примерно так:

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

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

Затем клавиша 'srcPhone' вместо этого представляет отношение, используя поле 'number' в целевом отношении отношения.

В ближайшее время я планирую добавить некоторую дополнительную работу в документацию о взаимоотношениях, поэтому, надеюсь, это будет более очевидно в будущем.

Ответ 2

(Не могу опубликовать это как комментарий слишком долго)

Том ответил выше, решив проблему.

Тем не менее, я также хочу, чтобы гиперссылковое поле возвращалось к ресурсу телефона. SlugRelatedField позволяет мне отправить целое поле, принадлежащее Телефону, но когда GETting результирующего ресурса вызова, он также сериализуется как целое число. Я уверен, что это предназначенная функциональность (не кажется очень изящной, что она сериализуется из целого числа, но к гиперссылке). Решение, которое я нашел, это добавить другое поле в CallSerializer: src = serializers.HyperlinkedRelatedField(view_name='phone-detail',source='srcPhone',blank=True,read_only=True) и добавить это поле в класс Meta. Затем я отправляю только POST только srcPhone (целое число) и GET srcPhone plus src, который является гиперссылкой на ресурс телефона.