2013-11-14 29 views
20

Zastanawiam się, czy ktoś miał Pythoniczne rozwiązanie łączące strukturę Django REST z django-polimorficznym.django-rest-framework + django-polimorficzna ModelSerialization

Dane:

class GalleryItem(PolymorphicModel): 
    gallery_item_field = models.CharField() 

class Photo(GalleryItem): 
    custom_photo_field = models.CharField() 

class Video(GalleryItem): 
    custom_image_field = models.CharField() 

Jeśli chcę listę wszystkich GalleryItems w Django-Rest-ram to tylko dać mi pola GalleryItem (model rodzic), stąd: id, gallery_item_field i polymorphic_ctype . Nie tego chcę. Chcę custom_photo_field, jeśli jest to instancja Photo i custom_image_field, jeśli jest to wideo.

Odpowiedz

24

tej pory tylko przetestowane na żądanie GET, a to działa:

class PhotoSerializer(serializers.ModelSerializer): 

    class Meta: 
     model = models.Photo 


class VideoSerializer(serializers.ModelSerializer): 

    class Meta: 
     model = models.Video 


class GalleryItemModuleSerializer(serializers.ModelSerializer): 

    class Meta: 
     model = models.GalleryItem 

    def to_representation(self, obj): 
     """ 
     Because GalleryItem is Polymorphic 
     """ 
     if isinstance(obj, models.Photo): 
      return PhotoSerializer(obj, context=self.context).to_representation(obj) 
     elif isinstance(obj, models.Video): 
      return VideoSerializer(obj, context=self.context).to_representation(obj) 
     return super(GalleryItemModuleSerializer, self).to_representation(obj) 

Dla POST i PUT żądań może chcesz zrobić coś podobnego jak przesłanianie definicji to_representation z def to_internal_value.

+1

nie mogę znaleźć żadnej dokumentacji na 'metody to_native'. Kiedy to się nazywa? –

+0

http://www.django-rest-framework.org/topics/3.0-announcement/#changes-to-the-custom-field-api –

+4

Zgaduję, że POST i PUT mogą być nieco trudniejsze (choć wciąż możliwe do wykonania), ponieważ musisz określić, co użytkownik chciał przesłać, PRZED sprawdzeniem poprawności (jeśli brakuje pola, może być niemożliwe uzyskanie pewności). IMHO, to czystsze używać oddzielnych punktów końcowych dla żądań zapisu. – WhyNotHugo

1

Dla uzupełnienia dodam implementację to_internal_value(), ponieważ potrzebowałem tego w moim ostatnim projekcie.

Jak określić typ

Jest poręczny mieć możliwość rozróżniania różnych „klas”; Więc dodałem właściwość typu do bazy polimorficznych modelu do tego celu:

class GalleryItem(PolymorphicModel): 
    gallery_item_field = models.CharField() 

    @property 
    def type(self): 
     return self.__class__.__name__ 

Pozwala to nazwać type jako „pole” i „tylko do odczytu pola”.

będzie zawierać nazwę klasy Python.

Dodawanie typu do serializer

Możesz dodać type na „polach” i „tylko do odczytu pola” (trzeba określić typ pola we wszystkich Serializers chociaż jeśli chcesz ich używać we wszystkich Modele dla dzieci)

class PhotoSerializer(serializers.ModelSerializer): 
    class Meta: 
     model = models.Photo 

    fields = (..., 'type',) 
    read_only_fields = (..., 'type',) 


class VideoSerializer(serializers.ModelSerializer): 
    class Meta: 
     model = models.Video 

    fields = (..., 'type',) 
    read_only_fields = (..., 'type',) 

class GalleryItemModuleSerializer(serializers.ModelSerializer): 
    class Meta: 
     model = models.GalleryItem 

    fields = (..., 'type',) 
    read_only_fields = (..., 'type',) 

    def to_representation(self, obj): 
     pass # see the other comment 

    def to_internal_value(self, data): 
    """ 
    Because GalleryItem is Polymorphic 
    """ 
    if data.get('type') == "Photo": 
     self.Meta.model = models.Photo 
     return PhotoSerializer(context=self.context).to_internal_value(data) 
    elif data.get('type') == "Video": 
     self.Meta.model = models.Video 
     return VideoSerializer(context=self.context).to_internal_value(data) 

    self.Meta.model = models.GalleryItem 
    return super(GalleryItemModuleSerializer, self).to_internal_value(data) 
4

Oto ogólne i wielokrotnego użytku rozwiązanie. Jest to generyczny kod Serializer, ale nie byłoby trudno go zmodyfikować, aby użyć ModelSerializer. Nie zajmuje się również serializowaniem klasy nadrzędnej (w moim przypadku używam klasy nadrzędnej bardziej jako interfejsu).

from typing import Dict 

from rest_framework import serializers 


class PolymorphicSerializer(serializers.Serializer): 
    """ 
    Serializer to handle multiple subclasses of another class 

    - For serialized dict representations, a 'type' key with the class name as 
     the value is expected: ex. {'type': 'Decimal', ... } 
    - This type information is used in tandem with get_serializer_map(...) to 
     manage serializers for multiple subclasses 
    """ 
    def get_serializer_map(self) -> Dict[str, serializers.Serializer]: 
     """ 
     Return a dict to map class names to their respective serializer classes 

     To be implemented by all PolymorphicSerializer subclasses 
     """ 
     raise NotImplementedError 

    def to_representation(self, obj): 
     """ 
     Translate object to internal data representation 

     Override to allow polymorphism 
     """ 
     type_str = obj.__class__.__name__ 

     try: 
      serializer = self.get_serializer_map()[type_str] 
     except KeyError: 
      raise ValueError(
       'Serializer for "{}" does not exist'.format(type_str), 
      ) 

     data = serializer(obj, context=self.context).to_representation(obj) 
     data['type'] = type_str 
     return data 

    def to_internal_value(self, data): 
     """ 
     Validate data and initialize primitive types 

     Override to allow polymorphism 
     """ 
     try: 
      type_str = data['type'] 
     except KeyError: 
      raise serializers.ValidationError({ 
       'type': 'This field is required', 
      }) 

     try: 
      serializer = self.get_serializer_map()[type_str] 
     except KeyError: 
      raise serializers.ValidationError({ 
       'type': 'Serializer for "{}" does not exist'.format(type_str), 
      }) 

     validated_data = serializer(context=self.context) \ 
      .to_internal_value(data) 
     validated_data['type'] = type_str 
     return validated_data 

    def create(self, validated_data): 
     """ 
     Translate validated data representation to object 

     Override to allow polymorphism 
     """ 
     serializer = self.get_serializer_map()[validated_data['type']] 
     return serializer(context=self.context).create(validated_data) 

I z niego korzystać:

class ParentClassSerializer(PolymorphicSerializer): 
    """ 
    Serializer for ParentClass objects 
    """ 
    def get_serializer_map(self) -> Dict[str, serializers.Serializer]: 
     """ 
     Return serializer map 
     """ 
     return { 
      ChildClass1.__name__: ChildClass1Serializer, 
      ChildClass2.__name__: ChildClass2Serializer, 
     } 
+1

Świetne rozwiązanie! Czy byłeś w stanie zintegrować to z przeglądalną dokumentacją DRF? –

+0

Dzięki, nie jestem zaznajomiony z dokumentami do przeglądania, więc nie. –