2013-08-04 13 views
6

Mam interfejs REST API django-rest-framework z zasobami hierarchicznymi. Chcę móc tworzyć podobiekty przez POSTing do /v1/objects/<pk>/subobjects/ i automatycznie ustawić klucz obcy na nowym obiekcie podrzędnym na kwarg z pk z adresu URL bez konieczności umieszczania go w polu danych. Obecnie serialalizator powoduje błąd 400, ponieważ oczekuje, że klucz obcy object znajdzie się w polu danych, ale nie powinien być również uważany za opcjonalny. Adres URL podobiektów to /v1/subobjects/<pk>/ (ponieważ klucz rodzica nie jest konieczny, aby go zidentyfikować), więc nadal jest wymagany, jeśli chcę PUT istniejącego zasobu.Django REST Framework: tworzenie obiektów hierarchicznych za pomocą argumentów URL

Czy powinienem zrobić tak, aby POST na /v1/subobjects/ z rodzica w ładunku, aby dodać podobiekty, czy jest tam czysty sposób przekazać kwarg z pk z adresu URL do serializera? Używam HyperlinkedModelSerializer i ModelViewSet jako moich odpowiednich klas bazowych. Czy jest jakiś zalecany sposób robienia tego? Dotychczas jedynym pomysłem było całkowite ponowne wdrożenie ViewSetów i stworzenie niestandardowej klasy Serializer, której get_default_fields() pochodzi ze słownika, który jest przekazywany z ViewSet, wypełniony przez jego kwargs. Wydaje się to dość zaangażowane w coś, co uważałbym za całkowicie run-of-the-mill, więc nie mogę przestać myśleć, że czegoś brakuje. Każdy interfejs REST API, jaki kiedykolwiek widziałem ma zapisywalne punkty końcowe, ma tego rodzaju oparte na adresie URL argumentowanie, więc fakt, że django-rest-framework nie wydaje się być w stanie to zrobić, wydaje się dziwny.

Odpowiedz

3

Wykonaj macierzyste pole serializera read_only. Nie jest to opcja opcjonalna, ale nie pochodzi też z danych żądania. Zamiast tego można wyciągnąć z PK/ślimak z adresu URL w pre_save() ...

# Assuming list and detail URLs like: 
# /v1/objects/<parent_pk>/subobjects/ 
# /v1/objects/<parent_pk>/subobjects/<pk>/ 
def pre_save(self, obj): 
    parent = models.MainObject.objects.get(pk=self.kwargs['parent_pk']) 
    obj.parent = parent 
+0

Funkcja 'pre_save' nie jest wywoływana aż do wystąpienia deserializacji, w którym to momencie wystąpił już błąd sprawdzania poprawności. –

+0

Ah, okay. Odpowiedź zaktualizowana. –

+0

Jest to jedna z opcji i działa na coś w rodzaju komentarzy, ale niektóre z obiektów, które mam, powinny być zdolne do powtórzenia, więc ustawienie rodzica jawnie w PUT byłoby miłe. Z drugiej strony, mógłbym to zrobić w ten sposób, a następnie mieć wyraźną akcję POST typu "reparent" lub coś podobnego, ale wydaje się to trochę mniej restrykcyjne. Przyjmuję tę odpowiedź, ponieważ jest to dobry pomysł i będzie działać w większości przypadków. –

1

Oto, co zrobiłem, aby go rozwiązać, chociaż byłoby miło, gdyby był bardziej ogólny sposób, ponieważ jest to taki typowy wzorzec adresów URL. Najpierw stworzył wstawek moich ViewSets że przedefiniować metody create:

class CreatePartialModelMixin(object): 
    def initial_instance(self, request): 
     return None 

    def create(self, request, *args, **kwargs): 
     instance = self.initial_instance(request) 
     serializer = self.get_serializer(
      instance=instance, data=request.DATA, files=request.FILES, 
      partial=True) 

     if serializer.is_valid(): 
      self.pre_save(serializer.object) 
      self.object = serializer.save(force_insert=True) 
      self.post_save(self.object, created=True) 
      headers = self.get_success_headers(serializer.data) 
      return Response(
       serializer.data, status=status.HTTP_201_CREATED, 
       headers=headers) 

     return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

Przeważnie jest ona skopiowana i wklejona z CreateModelMixin, ale definiuje metodę initial_instance że możemy zastąpić w podklasach aby stanowić punkt wyjścia dla serializatora , który jest skonfigurowany do częściowej deserializacji. Następnie można zrobić, na przykład,

class SubObjectViewSet(CreatePartialModelMixin, viewsets.ModelViewSet): 
    # .... 

    def initial_instance(self, request): 
     instance = models.SubObject(owner=request.user) 
     if 'pk' in self.kwargs: 
      parent = models.MainObject.objects.get(pk=self.kwargs['pk']) 
      instance.parent = parent 
     return instance 

(zdaję sobie sprawę, że w rzeczywistości nie trzeba zrobić .get na pk skojarzyć go od modelu, ale w moim przypadku jestem odsłaniając pocisk zamiast klucz podstawowy w publicznym interfejsie API)

0

Jeśli używasz ModelSerializer (który jest realizowany przez HyperlinkedModelSerializer) jest to tak proste, jak wdrożenie metody restore_object():

class MySerializer(serializers.ModelSerializer): 

    def restore_object(self, attrs, instance=None): 

     if instance is None: 
      # If `instance` is `None`, it means we're creating 
      # a new object, so we set the `parent_id` field. 
      attrs['parent_id'] = self.context['view'].kwargs['parent_pk'] 

     return super(MySerializer, self).restore_object(attrs, instance) 

    # ... 

restore_object() służy do deserializacji słownika atrybutów do instancji obiektu. ModelSerializerimplements this method i tworzy/aktualizuje instancję dla modelu określonego w klasie Meta. Jeśli podana wartość instance to None, oznacza to, że obiekt wciąż musi zostać utworzony, dlatego wystarczy dodać atrybut parent_id do argumentu attrs i wywołać super().

W ten sposób nie trzeba określać pola tylko do odczytu ani niestandardowego widoku/serializera.

Więcej informacji: http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers

Powiązane problemy