when i create a Serializer in django-rest0-framework, based on a ModelSerializer, i will have to pass the model in the Meta class:
class ClientSerializer(ModelSerializer):
class Meta:
model = Client
I want to create a general serializer which, based on the URL, includes the model dynamically.
My setup thusfar includes the urls.py and the viewset:
urls.py:
url(r'^api/v1/general/(?P<model>\w+)', kernel_api_views.GeneralViewSet.as_view({'get':'list'}))
and views.py:
class GeneralViewSet(viewsets.ModelViewSet):
def get_queryset(self):
# Dynamically get the model class from myapp.models
queryset = getattr(myapp.models, model).objects.all()
return queryset
def get_serializer_class(self):
return getattr(myapp.serializers, self.kwargs['model']+'Serializer')
Which in care of: http://127.0.0.1:8000/api/v1/general/Client gets Client.objects.all() as queryset and the ClientSerializer class as serializer
Question: How can i make it so that i can call 'GeneralSerializer' and dynamically assign the model in it?
The model serializer is just the same as the above serializer in the sense that it does the same job, only that it builds the serializer based on the model, making the creation of the serializer easier than building it normally. So in other words, explicitly defining CRUD methods (get, post,...) is not required.
Creating a basic Serializer To create a basic serializer one needs to import serializers class from rest_framework and define fields for a serializer just like creating a form or model in Django.
Serializer classes can also include reusable validators that are applied to the complete set of field data. These validators are included by declaring them on an inner Meta class. Also when you are defining a serializer then meta tags will help the serializer to bind that object in the specified format.
The . is_valid() method takes an optional raise_exception flag that will cause it to raise a serializers. ValidationError exception if there are validation errors.
You can do that by following:
serializers.py
class GeneralSerializer(serializers.ModelSerializer):
class Meta:
model = None
views.py
class GeneralViewSet(viewsets.ModelViewSet):
def get_queryset(self):
model = self.kwargs.get('model')
return model.objects.all()
def get_serializer_class(self):
GeneralSerializer.Meta.model = self.kwargs.get('model')
return GeneralSerializer
In serializers.py
, we define a GeneralSerializer
having model
in Meta
as None
. We'll override the model
value at the time of calling get_serializer_class()
.
Then in our views.py
file, we define a GeneralViewSet
with get_queryset()
and get_serializer_class()
overridden.
In get_queryset()
, we obtain the value of the model
from kwargs
and return that queryset.
In get_serializer_class()
, we set the value of model
for GeneralSerializer
to the value obtained from kwargs
and then return the GeneralSerializer
.
So far I know you cannot create a generic serializer if you use model serializer, but you can get the same solution using a base class and deriving all your models from that base class. Implement a method to return the serializer and then use that method to generate a dynamic serializer. I am using this technique for the last 2 years and working pretty fine for me -
class BaseModel(models.Model):
class Meta:
abstract = True # define abstract so that it does not cause any problem with model hierarchy in database
@classmethod
def get_serializer(cls):
class BaseSerializer(serializers.ModelSerializer):
class Meta:
model = cls # this is the main trick here, this is how I tell the serializer about the model class
return BaseSerializer #return the class object so we can use this serializer
Now derive your models from it -
class Derived1(BaseModel):
pass
class Derived2(BaseModel):
pass
if you want to override the serializer then just do it in the one that you need. for example -
class DerivedOverride(BaseModel):
@classmethod
def get_serializer(cls):
super_serializer = BaseModel.get_serializer() # this important to not to break the serializing hierarchy
class BaseSerializer(super_serializer):
class Meta:
model = cls # this is the main trick here, this is how I tell the serializer about the model class
return BaseSerializer
Thats it, now each class has its own dynamic serializer but we just defined it in one place.
Now use the serializer in view set -
class Derive1ViewSet(ModelViewSet):
serializer_class = Derived1.get_serializer()
class Derive2ViewSet(ModelViewSet):
serializer_class = Derived2.get_serializer()
and go on from there.
To build on Rahul's answer, this is what worked for me:
urls.py
url(r'^api/(?P<app_label>\w+)/(?P<model_name>\w+)', GeneralViewSet.as_view({'get': 'list'}))
serializers.py
from rest_framework import serializers
class GeneralSerializer(serializers.ModelSerializer):
class Meta:
model = None
views.py
from django.apps import apps
class GeneralViewSet(viewsets.ModelViewSet):
@property
def model(self):
return apps.get_model(app_label=str(self.kwargs['app_label']), model_name=str(self.kwargs['model_name']))
def get_queryset(self):
model = self.model
return model.objects.all()
def get_serializer_class(self):
GeneralSerializer.Meta.model = self.model
return GeneralSerializer
In addition to @Rahul and @btal, we can use decorator pattern on ModelSerializer
in case if we want to use APIView
.
def getGenericSerializer(model_arg):
class GenericSerializer(serializers.ModelSerializer):
class Meta:
model = model_arg
fields = '__all__'
return GenericSerializer
And use it like this in the APIView:
class MyView(APIView):
def get(self, request, format=None):
#...
GenericSzl = getGenericSerializer(model)
serializer = GenericSzl(objs, many=True)
return Response(serializer.data)
Hope this helps to those people who don't want to use ModelViewSet
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With