2013-01-16 5 views
12

Gần đây tôi đã bắt đầu sử dụng khung REST Django (và Django, và Python - tôi là một người hệ thống RTOS/nhúng!) Để thực hiện một RESTful Web API. Chưa có bất kỳ vấn đề nào chưa được giải quyết với Google, nhưng điều này đã khiến tôi bối rối trong một vài giờ.Khung hình Django REST - Đăng trường khóa ngoại có chứa khóa tự nhiên?

Tôi có một hệ thống nhúng lắng nghe các sự kiện được liên kết với một loạt các thiết bị - tương tự như một cuộc gọi điện thoại, đó là những gì tôi sẽ thảo luận ở đây ngắn gọn. Một điện thoại có một số và rất nhiều cuộc gọi (mà nó đã thực hiện) liên kết với nó. Một cuộc gọi có một điện thoại liên quan (điện thoại đã thực hiện cuộc gọi) và một thời gian sáng tạo. Khi một cuộc gọi xảy ra, nó sẽ được gửi đến API. Tôi có một hệ thống nhúng lắng nghe các cuộc gọi và số điện thoại gốc của họ và gửi chúng đến API. Vì hệ thống nhúng biết số điện thoại, tôi muốn gửi số điện thoại: {"srcPhone":12345678} thay vì {"srcPhone":"http://host/phones/5"}. Điều này tránh sự cần thiết cho hệ thống nhúng của tôi để biết khóa chính của mỗi điện thoại (hoặc để nhận được điện thoại theo số mỗi khi nó muốn gửi một cuộc gọi).

Google và tài liệu Django đề xuất tôi có thể đạt được điều này bằng các khóa tự nhiên. nỗ lực của tôi sau:

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 kiểm tra, tôi tạo một Điện thoại với số 123456. Sau đó, tôi POST {"srcPhone": 123456} đến http://host/calls/ (được cấu hình trong urls.py để chạy chế độ xem Danh sách cuộc gọi). Điều này cho một đối tượng AttributeError tại/calls/- 'int' không có thuộc tính 'startswith'. Ngoại lệ xảy ra trong rest_framework/relations.py (dòng 355). Có thể đăng toàn bộ dấu vết nếu nó sẽ hữu ích. Khi đọc relations.py, có vẻ như khung REST không tra cứu điện thoại theo số, nhưng xử lý thuộc tính srcPhone như thể nó là một URL. Điều này thường đúng, nhưng tôi muốn nó tìm kiếm Điện thoại bằng khóa tự nhiên, thay vì cung cấp URL. Tôi đã bỏ lỡ điều gì ở đây?

Cảm ơn!

Trả lời

13

Những gì bạn đang tìm kiếm là SlugRelatedField. Xem docs here.

nhưng xử lý thuộc tính srcPhone như thể đó là URL.

Chính xác. Bạn đang sử dụng HyperlinkedModelSerializer, do đó, khóa srcPhone đang sử dụng mối quan hệ siêu liên kết theo mặc định.

Trường hợp ngoại lệ 'int' object has no attribute 'startswith' bạn thấy là bởi vì nó đang mong đợi chuỗi URL, nhưng nhận được số nguyên. Thực sự là phải dẫn đến lỗi xác thực mô tả, vì vậy tôi đã created a ticket for that.

Nếu bạn thay vì sử dụng một cái gì đó serializer như thế này:

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

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

Sau đó, chìa khóa 'srcPhone' sẽ thay đại diện cho mối quan hệ bằng trường 'number' trên mục tiêu của mối quan hệ.

Tôi dự định sẽ sớm đưa thêm một số công việc vào tài liệu quan hệ, vì vậy hy vọng điều này sẽ rõ ràng hơn trong tương lai.

+0

Cảm ơn Tom - bây giờ tôi có thể gửi các cuộc gọi chỉ với số srcPhone số nguyên. – EwanC

+0

Chỉ tìm thấy câu trả lời này và giải quyết vấn đề của tôi! Rất tốt đẹp giải thích – zeroliu

1

(Không thể đăng bài này như một lời nhận xét, quá dài)

câu trả lời của Tom trên giải quyết vấn đề.

Tuy nhiên, tôi cũng muốn có trường siêu liên kết quay lại tài nguyên Điện thoại. SlugRelatedField cho phép tôi gửi với một trường số nguyên thuộc về điện thoại, nhưng khi GET kết quả tài nguyên cuộc gọi, nó cũng nối tiếp như một số nguyên. Tôi chắc chắn đây là chức năng dự định (không có vẻ rất thanh lịch để có nó serialising từ một số nguyên, nhưng để một siêu liên kết). Giải pháp tôi tìm thấy là thêm một trường khác vào CallSerializer: src = serializers.HyperlinkedRelatedField(view_name='phone-detail',source='srcPhone',blank=True,read_only=True) và thêm trường đó vào lớp Meta. Sau đó, tôi POST chỉ srcPhone (một số nguyên) và GET srcPhone plus src, là một siêu liên kết đến tài nguyên Điện thoại.

+0

Tôi không chắc chắn rằng bạn cần rằng 'blank = True', nhưng nếu không, yup, có vẻ tốt. –