2013-01-16 11 views
12

Niedawno zacząłem używać Django REST Framework (i Django, i Python - jestem osoba z systemem RTOS/embedded systems!) W celu implementacji RESTful Web API. Nie miałem jeszcze żadnych problemów, które nie mogłyby zostać rozwiązane przez Google, ale ten sprawił, że przez kilka godzin nie mogłem się z tym pogodzić.Django REST Framework - POSTing pole klucza obcego zawierające klucz naturalny?

Mam wbudowany system, który nasłuchuje zdarzeń, które są powiązane z szeregiem urządzeń - analogicznie do telefonu wywołującego połączenia, o czym będę tu dyskutować ze zwięzłością. Telefon ma przypisaną liczbę i wiele połączeń (wykonanych). Połączenie ma powiązany telefon (telefon, który wykonał połączenie) i czas utworzenia. Gdy wystąpi połączenie, należy je wysłać do interfejsu API. Mam wbudowany system, który nasłuchuje połączeń i ich początkowy numer telefonu i przesyła je do interfejsu API. Ponieważ system wbudowany zna numer telefonu, chciałbym go przesłać: {"srcPhone":12345678} zamiast {"srcPhone":"http://host/phones/5"}. Dzięki temu mój system wbudowany nie musi znać klucza podstawowego każdego telefonu (lub telefonów GET według numeru za każdym razem, gdy chce przesłać połączenie).

Dokumenty Google i Django sugerują, że mogę to osiągnąć za pomocą naturalnych kluczy. Moja próba następująco:

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 przetestuj, tworzę telefon z numerem 123456. Następnie POST {"srcPhone": 123456} do http://host/calls/ (który jest skonfigurowany w urls.py, aby uruchomić widok Lista połączeń). To daje atrybut AttributeError at/calls/- 'int' obiekt nie ma atrybutu "startswith". Wyjątek występuje w rest_framework/relations.py (linia 355). Możesz opublikować cały ślad, jeśli będzie to pomocne. Po przeczytaniu relations.py wygląda na to, że REST Framework nie szuka telefonów po numerze, ale przetwarza atrybut srcPhone tak, jakby był adresem URL. Normalnie byłoby to prawdą, ale chcę, aby wyszukiwał telefony za pomocą klucza naturalnego, zamiast podawać adres URL. Czego tu brakowało?

Dzięki!

Odpowiedz

13

To, czego szukasz, to SlugRelatedField. Zobacz docs here.

ale przetwarzanie atrybutu srcPhone tak, jakby był adresem URL.

Dokładnie. Używasz HyperlinkedModelSerializer, więc klucz srcPhone domyślnie używa relacji hiperłącza.

Wyjątek 'int' object has no attribute 'startswith', który widzisz, polega na oczekiwaniu na ciąg adresu URL, ale na otrzymaniu liczby całkowitej. Naprawdę powinno to skutkować opisowym błędem walidacji, więc mam created a ticket for that.

Jeśli zamiast używać serializer coś takiego:

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

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

następnie klawisz 'srcPhone' będzie zamiast reprezentowania relacji przy użyciu pola 'number' na tarczy relacji.

W najbliższym czasie planuję włożyć trochę pracy do dokumentacji relacji, więc mam nadzieję, że będzie to bardziej oczywiste w przyszłości.

+0

Dziękuję za to Toma - teraz mogę przesyłać połączenia z całkowitą liczbą srcPhone. – EwanC

+0

Właśnie znalazłem tę odpowiedź i rozwiązałem również mój problem! Bardzo ładnie wyjaśnił – zeroliu

1

(nie mogę pisać to jako komentarz, zbyt długo)

Toma odpowiedź powyżej rozwiązało problem.

Jednak chcę również, aby pole z odsyłaczem hipertekstowym powróciło do zasobu telefonu. SlugRelatedField pozwala mi przesłać z polem całkowitym należącym do telefonu, ale po pobraniu powstałego zasobu Call, również serializuje jako liczbę całkowitą. Jestem pewny, że jest to zamierzona funkcjonalność (nie wydaje się zbyt elegancka, aby szeregować ją z liczby całkowitej, ale z hiperłącza). Rozwiązaniem, które znalazłem, było dodanie kolejnego pola do CallSerializera: src = serializers.HyperlinkedRelatedField(view_name='phone-detail',source='srcPhone',blank=True,read_only=True) i dodanie tego pola do klasy Meta. Następnie POST tylko srcPhone (liczba całkowita) i GET srcPhone plus src, które jest hiperłączem do zasobu telefonu.

+0

Nie jestem pewien, czy potrzebujesz tego 'puste = True', ale poza tym, tak, wygląda dobrze. –

Powiązane problemy