Ho recentemente iniziato a utilizzare il Django REST Framework (e Django e Python - Sono un utente di sistemi RTOS/embedded!) Per implementare un'API Web RESTful. Non ho ancora avuto problemi che non possano essere risolti con Google, ma questo mi ha costretto per un paio di ore.Django REST Framework - POSTing chiave esterna contenente campo chiave naturale?
Ho un sistema incorporato che ascolta gli eventi associati a una gamma di dispositivi, analogamente a un telefono che effettua chiamate, che è quello che discuterò qui per brevità. Un telefono ha un numero e un sacco di chiamate (che ha fatto) associate ad esso. Una chiamata ha un telefono associato (il telefono che ha effettuato la chiamata) e un orario di creazione. Quando si verifica una chiamata, è necessario eseguire il POST sull'API. Ho un sistema incorporato che ascolta le chiamate e il loro numero di telefono di origine e li invia all'API. Poiché il sistema incorporato conosce il numero di telefono, desidero inviare: {"srcPhone":12345678}
anziché {"srcPhone":"http://host/phones/5"}
. Ciò evita la necessità che il mio sistema embedded conosca la chiave primaria di ogni telefono (o GET Phones per numero ogni volta che desidera inviare una chiamata).
Google e i documenti di Django suggerivano che avrei potuto ottenere questo con chiavi naturali. Il mio tentativo segue:
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')
T o test, creo un telefono con il numero 123456. Quindi I POST {"srcPhone": 123456} a http://host/calls/
(che è configurato in urls.py per eseguire la vista CallList). Questo dà un AttributeError a/calls/- l'oggetto 'int' non ha attributo 'startswith'. L'eccezione si verifica in rest_framework/relations.py (riga 355). Può pubblicare l'intera traccia se sarà utile. Leggendo Relations.py, sembra che REST Framework non stia cercando Telefoni per numero, ma elaborando l'attributo srcPhone come se fosse un URL. Questo sarebbe normalmente vero, ma voglio che cerchi Phones per chiave naturale, piuttosto che fornire l'URL. Cosa mi sono perso qui?
Grazie!
Grazie per questo Tom, ora posso inviare chiamate solo con un numero intero di srcPhone. – EwanC
Ho appena trovato questa risposta e risolto il mio problema! Molto bello spiegato – zeroliu