2013-01-16 8 views
12

Ich habe vor kurzem begonnen, das Django REST Framework (und Django und Python - ich bin ein RTOS/embedded Systems Person!) Zu verwenden, um eine RESTful Web API zu implementieren. Ich hatte noch keine Probleme, die mit Google nicht gelöst werden konnten, aber diese hat mich jetzt für ein paar Stunden ratlos gemacht.Django REST Framework - POST-Fremdschlüsselfeld mit natürlichem Schlüssel?

Ich habe ein Embedded-System, das auf Ereignisse abhört, die mit einer Reihe von Geräten verbunden sind - analog zu einem Telefon, das Anrufe tätigt, was ich hier kurz diskutieren werde. Ein Telefon hat eine Nummer und eine ganze Reihe von Anrufen (die es gemacht hat). Ein Anruf hat ein zugeordnetes Telefon (das Telefon, das den Anruf getätigt hat) und eine Erstellungszeit. Wenn ein Anruf auftritt, sollte er an die API gesendet werden. Ich habe ein eingebettetes System, das auf Anrufe und ihre Ursprungs-Telefonnummer wartet und sie an die API übermittelt. Da das Embedded-System die Telefonnummer kennt, möchte ich folgendes einreichen: {"srcPhone":12345678} statt {"srcPhone":"http://host/phones/5"}. Dies vermeidet die Notwendigkeit, dass mein eingebettetes System den Primärschlüssel jedes Telefons kennt (oder jedes Mal, wenn es einen Anruf einreichen möchte, Telefone nach Nummer abruft).

Google und die Django-Dokumente schlugen vor, dass ich dies mit natürlichen Schlüsseln erreichen könnte. Mein Versuch folgt:

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, ich erstelle ein Telefon mit der Nummer 123456. Dann POST {"srcPhone": 123456} zu http://host/calls/ (die in urls.py konfiguriert ist, um die CallList-Ansicht auszuführen). Dies ergibt einen AttributeError bei/calls/- 'int' Objekt hat kein Attribut 'startswith'. Die Ausnahme tritt in rest_framework/relations.py (Zeile 355) auf. Kann die gesamte Nachricht posten, wenn sie hilfreich ist. Beim Lesen von relations.py sieht es so aus, als ob REST Framework die Telefone nicht nach Nummer sucht, sondern das srcPhone-Attribut so verarbeitet, als wäre es eine URL. Das wäre normalerweise richtig, aber ich möchte, dass die Telefone nach dem natürlichen Schlüssel suchen, anstatt die URL anzugeben. Was habe ich hier verpasst?

Danke!

Antwort

13

Was Sie suchen, ist SlugRelatedField. Siehe docs here.

aber das srcPhone-Attribut verarbeitet, als ob es eine URL wäre.

Genau. Sie verwenden HyperlinkedModelSerializer, so dass der Schlüssel srcPhone standardmäßig eine Hyperlink-Beziehung verwendet.

Die Ausnahme 'int' object has no attribute 'startswith', die Sie sehen, liegt daran, dass eine URL-Zeichenfolge erwartet wird, die aber eine ganze Zahl empfängt. Wirklich, das sollte zu einem beschreibenden Validierungsfehler führen, also habe ich created a ticket for that.

Wenn Sie stattdessen einen Serializer etwas wie folgt verwenden:

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

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

Dann wird der 'srcPhone' Taste wird stattdessen die Beziehung darstellen auf dem Ziel der Beziehung der 'number' Feld.

Ich habe vor, irgendwann in der Beziehungsdokumentation etwas mehr Arbeit zu erledigen, also wird dies hoffentlich in Zukunft deutlicher.

+0

Danke für das Tom - jetzt kann ich Anrufe nur mit einem Integer srcPhone Nummer senden. – EwanC

+0

Gerade gefunden diese Antwort und löste mein Problem auch! Sehr schön erklärt – zeroliu

1

(kann nicht schreiben als Kommentar, zu lang)

Tom Antwort über das Problem gelöst.

Allerdings möchte ich auch ein Hyperlink-Feld zurück zur Telefon-Ressource haben. Das SlugRelatedField ermöglicht es mir, mit einem Integer-Feld zu senden, das zu dem Phone gehört, aber wenn die resultierende Call-Ressource abgerufen wird, wird es auch als Ganzzahl serialisiert. Ich bin mir sicher, dass dies die beabsichtigte Funktionalität ist (es scheint nicht sehr elegant zu sein, sie von einer Ganzzahl zu serialisieren, sondern zu einem Hyperlink). Die Lösung, die ich gefunden habe, war, dem CallSerializer ein weiteres Feld hinzuzufügen: src = serializers.HyperlinkedRelatedField(view_name='phone-detail',source='srcPhone',blank=True,read_only=True) und dieses Feld der Meta-Klasse hinzuzufügen. Dann poste ich nur srcPhone (eine ganze Zahl) und GET srcPhone plus src, was ein Hyperlink zu der Telefonressource ist.

+0

Ich bin mir nicht sicher, dass Sie das brauchen blank = True, aber sonst, yup, das sieht gut aus. –

Verwandte Themen