Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django rest framework abstract class serializer

Tags:

I have some models like these:

class TypeBase(models.Model):
    name = models.CharField(max_length=20)
    class Meta:
        abstract=True

class PersonType(TypeBase):
    pass

class CompanyType(TypeBase):
    pass

Having this, I want to create just one serializer that holds all these field types (serialization, deserialization, update and save).

To be more specific, I want only one serializer (TypeBaseSerializer) that print the Dropdown on the UI, serialize the json response, deserialize it on post and save it for all my based types.

Something like this:

class TypeBaseSerializer(serializers.Serializer):
    class Meta:
        model = TypeBase
        fields = ('id', 'name')

Is it possible?

like image 813
Tiago Framesqui Avatar asked Oct 14 '15 23:10

Tiago Framesqui


People also ask

Why do we need Serializers in Django REST Framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

How do I pass Queryset to serializer?

To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.

How do you pass extra context data to Serializers in Django REST Framework?

In function based views we can pass extra context to serializer with "context" parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with "self. context". From example, to get "exclude_email_list" we just used code 'exclude_email_list = self.

What is class Meta in Django serializer?

It serves the same point as using a Meta class object inside a Django model class, or a form class, etc. It's a standard pattern to attach configuration (metadata) to a set of fields that make up the model, form, or in this case, serializer.


4 Answers

I think the following approach is more cleaner. You can set "abstract" field to true for the base serializer and add your common logic for all child serializers.

class TypeBaseSerializer(serializers.ModelSerializer):
    class Meta:
        model = TypeBase
        fields = ('id', 'name')
        abstract = True

    def func(...):
    # ... some logic

And then create child serializers and use them for data manipulation.

class PersonTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = PersonType
        fields = ('id', 'name')


class CompanyTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = CompanyType
        fields = ('id', 'name')

Now you can use the both of these serializers normally for every model.

But if you really want to have one serializers for both the models, then create a container model and a serializer for him too. That is much cleaner :)

like image 54
adkl Avatar answered Sep 22 '22 19:09

adkl


You can't use a ModelSerializer with an abstract base model. From restframework.serializers:

if model_meta.is_abstract_model(self.Meta.model):
        raise ValueError(
            'Cannot use ModelSerializer with Abstract Models.'
        )

I wrote a serializer_factory function for a similar problem:

from collections import OrderedDict
from restframework.serializers import ModelSerializer
def serializer_factory(mdl, fields=None, **kwargss):
""" Generalized serializer factory to increase DRYness of code.

:param mdl: The model class that should be instanciated
:param fields: the fields that should be exclusively present on the serializer
:param kwargss: optional additional field specifications
:return: An awesome serializer
"""

    def _get_declared_fields(attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)
        return OrderedDict(fields)

    # Create an object that will look like a base serializer
    class Base(object):
        pass

    Base._declared_fields = _get_declared_fields(kwargss)

    class MySerializer(Base, ModelSerializer):
        class Meta:
            model = mdl

        if fields:
            setattr(Meta, "fields", fields)

    return MySerializer

You can then use the factory to produce serializers as needed:

def typebase_serializer_factory(mdl):
    myserializer = serializer_factory(
        mdl,fields=["id","name"],
        #owner=HiddenField(default=CurrentUserDefault()),#Optional additional configuration for subclasses 
      )
    return myserializer

Now instanciate different subclass serializers:

persontypeserializer = typebase_serializer_factory(PersonType)
companytypeserializer = typebase_serializer_factory(CompanyType)
like image 35
Sebastian Wozny Avatar answered Sep 22 '22 19:09

Sebastian Wozny


Just iterating a bit over @adki's answer:

  1. it is possible to skip model for TypeBaseSerializer;
  2. derived serializers can refer to TypeBaseSerializer.Meta, so you would change them in a single place.
class TypeBaseSerializer(serializers.Serializer):
    class Meta:
        fields = ('id', 'name', 'created')
        abstract = True

    def func(...):
    # ... some logic

class PersonTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = PersonType
        fields = TypeBaseSerializer.Meta.fields + ('age', 'date_of_birth')

class CompanyTypeSerializer(TypeBaseSerializer):
    class Meta:
        model = CompanyType
        fields = TypeBaseSerializer.Meta.fields
like image 39
Kostyantyn Avatar answered Sep 24 '22 19:09

Kostyantyn


As already mentioned in Sebastian Wozny's answer, you can't use a ModelSerializer with an abstract base model.

Also, there is nothing such as an abstract Serializer, as some other answers have suggested. So setting abstract = True on the Meta class of a serializer will not work.

However you need not use use a ModelSerializer as your base/parent serializer. You can use a Serializer and then take advantage of Django's multiple inheritance. Here is how it works:

class TypeBaseSerializer(serializers.Serializer):
    # Need to re-declare fields since this is not a ModelSerializer
    name = serializers.CharField()
    id = serializers.CharField()

    class Meta:
        fields = ['id', 'name']

    def someFunction(self):
        #... will be available on child classes ...
        pass

class PersonTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):

    class Meta:
        model = PersonType
        fields = TypeBaseSerializer.Meta.fields + ['another_field']


class CompanyTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):

    class Meta:
        model = CompanyType
        fields = TypeBaseSerializer.Meta.fields + ['some_other_field']

So now since the fields name and id are declared on the parent class (TypeBaseSerializer), they will be available on PersonTypeSerializer and since this is a child class of ModelSerializer those fields will be populated from the model instance.

You can also use SerializerMethodField on the TypeBaseSerializer, even though it is not a ModelSerializer.

class TypeBaseSerializer(serializers.Serializer):
    # you will have to re-declare fields here since this is not a ModelSerializer
    name = serializers.CharField()
    id = serializers.CharField()
    other_field = serializers.SerializerMethodField()

    class Meta:
        fields = ['id', 'name', 'other_field']

    def get_other_field(self, instance):
        # will be available on child classes, which are children of ModelSerializers
        return instance.other_field
like image 33
GunnerFan Avatar answered Sep 25 '22 19:09

GunnerFan