436
Override your get_serializer_class
method. This method is used in your model mixins to retrieve the proper Serializer class.
Note that there is also a get_serializer
method which returns an instance of the correct Serializer
class DualSerializerViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == 'list':
return serializers.ListaGruppi
if self.action == 'retrieve':
return serializers.DettaglioGruppi
return serializers.Default # I dont' know what you want for create/destroy/update.
110
You may find this mixin useful, it overrides the get_serializer_class method and allows you to declare a dict that maps action and serializer class or fallback to the usual behavior.
class MultiSerializerViewSetMixin(object):
def get_serializer_class(self):
"""
Look for serializer class in self.serializer_action_classes, which
should be a dict mapping action name (key) to serializer class (value),
i.e.:
class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
serializer_class = MyDefaultSerializer
serializer_action_classes = {
'list': MyListSerializer,
'my_action': MyActionSerializer,
}
@action
def my_action:
...
If there's no entry for that action then just fallback to the regular
get_serializer_class lookup: self.serializer_class, DefaultSerializer.
"""
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(MultiSerializerViewSetMixin, self).get_serializer_class()
- [Django]-Django – getting Error "Reverse for 'detail' with no arguments not found. 1 pattern(s) tried:" when using {% url "music:fav" %}
- [Django]-Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?
- [Django]-Django Installed Apps Location
54
This answer is the same as the accepted answer but I prefer to do in this way.
Generic views
Returns the class that should be used for the serializer. Defaults to returning the
serializer_class
attribute.May be overridden to provide dynamic behavior, such as using different serializers for reading and write operations or providing different serializers to the different types of users.
the serializer_class attribute.
class DualSerializerViewSet(viewsets.ModelViewSet):
# mapping serializer into the action
serializer_classes = {
'list': serializers.ListaGruppi,
'retrieve': serializers.DettaglioGruppi,
# ... other actions
}
default_serializer_class = DefaultSerializer # Your default serializer
def get_serializer_class(self):
return self.serializer_classes.get(self.action, self.default_serializer_class)
- [Django]-Many-To-Many Fields View on Django Admin
- [Django]-IOS app with Django
- [Django]-Django apps aren't loaded yet when using asgi
18
Regarding providing different serializers, why is nobody going for the approach that checks the HTTP method? It’s clearer IMO and requires no extra checks.
def get_serializer_class(self):
if self.request.method == 'POST':
return NewRackItemSerializer
return RackItemSerializer
Credits/source: https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718
- [Django]-Django form: what is the best way to modify posted data before validating?
- [Django]-How do I do a not equal in Django queryset filtering?
- [Django]-What does 'many = True' do in Django Rest FrameWork?
11
Based on @gonz and @user2734679 answers I’ve created this small python package that gives this functionality in form a child class of ModelViewset. Here is how it works.
from drf_custom_viewsets.viewsets.CustomSerializerViewSet
from myapp.serializers import DefaltSerializer, CustomSerializer1, CustomSerializer2
class MyViewSet(CustomSerializerViewSet):
serializer_class = DefaultSerializer
custom_serializer_classes = {
'create': CustomSerializer1,
'update': CustomSerializer2,
}
- [Django]-How can I upgrade specific packages using pip and a requirements file?
- [Django]-Charts in django Web Applications
- [Django]-How to test Django's UpdateView?
11
Just want to addon to existing solutions. If you want a different serializer for your viewset’s extra actions (i.e. using @action
decorator), you can add kwargs in the decorator like so:
@action(methods=['POST'], serializer_class=YourSpecialSerializer)
def your_extra_action(self, request):
serializer = self.get_serializer(data=request.data)
...
- [Django]-How to run a celery worker with Django app scalable by AWS Elastic Beanstalk?
- [Django]-Django models: mutual references between two classes and impossibility to use forward declaration in python
- [Django]-Django REST Framework (DRF): Set current user id as field value
2
With all other solutions mentioned, I was unable to find how to instantiate the class using get_serializer_class
function and unable to find custom validation function as well. For those who are still lost just like I was and want full implementation please check the answer below.
views.py
from rest_framework.response import Response
from project.models import Project
from project.serializers import ProjectCreateSerializer, ProjectIDGeneratorSerializer
class ProjectViewSet(viewsets.ModelViewSet):
action_serializers = {
'generate_id': ProjectIDGeneratorSerializer,
'create': ProjectCreateSerializer,
}
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if hasattr(self, 'action_serializers'):
return self.action_serializers.get(self.action, self.serializer_class)
return super(ProjectViewSet, self).get_serializer_class()
# You can create custom function
def generate_id(self, request):
serializer = self.get_serializer_class()(data=request.GET)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
def create(self, request, **kwargs):
serializer = self.get_serializer_class()(data=request.data)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
serializers.py
import random
from rest_framework import serializers
from project.models import Project
class ProjectIDGeneratorSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
class Meta:
fields = ['projectName']
def validate(self, attrs):
project_name = attrs.get('projectName')
project_id = project_name.replace(' ', '-')
return {'projectID': project_id}
class ProjectCreateSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
projectID = serializers.CharField(write_only=True)
class Meta:
model = Project
fields = ['projectName', 'projectID']
def to_representation(self, instance: Project):
data = dict()
data['projectName'] = instance.name
data['projectID'] = instance.projectID
data['createdAt'] = instance.createdAt
data['updatedAt'] = instance.updatedAt
representation = {
'message': f'Project {instance.name} has been created.',
}
return representation
def validate(self, attrs):
print('attrs', dict(attrs))
project_name = attrs.get('projectName')
project_id = attrs.get('projectID')
if Project.objects.filter(projectID=project_id).first():
raise serializers.ValidationError(f'Project with ID {project_id} already exist')
project = Project.objects.create(projectID=project_id,
name=project_name)
print('user', self.context['user'])
project.user.add(self.context["user"])
project.save()
return self.to_representation(project)
urls.py
from django.urls import path
from .views import ProjectViewSet
urlpatterns = [
path('project/generateID', ProjectViewSet.as_view({'get': 'generate_id'})),
path('project/create', ProjectViewSet.as_view({'post': 'create'})),
]
models.py
# Create your models here.
from django.db import models
from authentication.models import User
class Project(models.Model):
id = models.AutoField(primary_key=True)
projectID = models.CharField(max_length=255, blank=False, db_index=True, null=False)
user = models.ManyToManyField(User)
name = models.CharField(max_length=255, blank=False)
createdAt = models.DateTimeField(auto_now_add=True)
updatedAt = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
- [Django]-Django character set with MySQL weirdness
- [Django]-Django DRF with oAuth2 using DOT (django-oauth-toolkit)
- [Django]-No module named pkg_resources
1
Although pre-defining multiple Serializers in or way or another does seem to be the most obviously documented way, FWIW there is an alternative approach that draws on other documented code and which enables passing arguments to the serializer as it is instantiated. I think it would probably tend to be more worthwhile if you needed to generate logic based on various factors, such as user admin levels, the action being called, perhaps even attributes of the instance.
The first piece of the puzzle is the documentation on dynamically modifying a serializer at the point of instantiation. That documentation doesn’t explain how to call this code from a viewset or how to modify the readonly status of fields after they’ve been initated – but that’s not very hard.
The second piece – the get_serializer method is also documented – (just a bit further down the page from get_serializer_class under ‘other methods’) so it should be safe to rely on (and the source is very simple, which hopefully means less chance of unintended side effects resulting from modification). Check the source under the GenericAPIView (the ModelViewSet – and all the other built in viewset classes it seems – inherit from the GenericAPIView which, defines get_serializer.
Putting the two together you could do something like this:
In a serializers file (for me base_serializers.py):
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Adding this next line to the documented example
read_only_fields = kwargs.pop('read_only_fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# another bit we're adding to documented example, to take care of readonly fields
if read_only_fields is not None:
for f in read_only_fields:
try:
self.fields[f].read_only = True
exceptKeyError:
#not in fields anyway
pass
Then in your viewset you might do something like this:
class MyViewSet(viewsets.ModelViewSet):
# ...permissions and all that stuff
def get_serializer(self, *args, **kwargs):
# the next line is taken from the source
kwargs['context'] = self.get_serializer_context()
# ... then whatever logic you want for this class e.g:
if self.action == "list":
rofs = ('field_a', 'field_b')
fs = ('field_a', 'field_c')
if self.action == “retrieve”:
rofs = ('field_a', 'field_c’, ‘field_d’)
fs = ('field_a', 'field_b’)
# add all your further elses, elifs, drawing on info re the actions,
# the user, the instance, anything passed to the method to define your read only fields and fields ...
# and finally instantiate the specific class you want (or you could just
# use get_serializer_class if you've defined it).
# Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
kwargs['read_only_fields'] = rofs
kwargs['fields'] = fs
return MyDynamicSerializer(*args, **kwargs)
And that should be it! Using MyViewSet should now instantiate your MyDynamicSerializer with the arguments you’d like – and assuming your serializer inherits from your DynamicFieldsModelSerializer, it should know just what to do.
Perhaps its worth mentioning that it can makes special sense if you want to adapt the serializer in some other ways …e.g. to do things like take in a read_only_exceptions list and use it to whitelist rather than blacklist fields (which I tend to do). I also find it useful to set the fields to an empty tuple if its not passed and then just remove the check for None … and I set my fields definitions on my inheriting Serializers to ‘all‘. This means no fields that aren’t passed when instantiating the serializer survive by accident and I also don’t have to compare the serializer invocation with the inheriting serializer class definition to know what’s been included…e.g within the init of the DynamicFieldsModelSerializer:
# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....
NB If I just wanted two or three classes that mapped to distinct actions and/or I didn’t want any specially dynamic serializer behaviour, I might well use one of the approaches mentioned by others here, but I thought this worth presenting as an alternative, particularly given its other uses.
- [Django]-How to go from django image field to PIL image and back?
- [Django]-How to understand lazy function in Django utils functional module
- [Django]-Numeric for loop in Django templates
0
You can map your all serializers with the action using a dictionary in class and then get them from "get_serializer_class" method. Here is what I am using to get different serializers in different cases.
class RushesViewSet(viewsets.ModelViewSet):
serializer_class = DetailedRushesSerializer
queryset = Rushes.objects.all().order_by('ingested_on')
permission_classes = (IsAuthenticated,)
filter_backends = (filters.SearchFilter,
django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter)
pagination_class = ShortResultsSetPagination
search_fields = ('title', 'asset_version__title',
'asset_version__video__title')
filter_class = RushesFilter
action_serializer_classes = {
"create": RushesSerializer,
"update": RushesSerializer,
"retrieve": DetailedRushesSerializer,
"list": DetailedRushesSerializer,
"partial_update": RushesSerializer,
}
def get_serializer_context(self):
return {'request': self.request}
def get_serializer_class(self):
try:
return self.action_serializer_classes[self.action]
except (KeyError, AttributeError):
error_logger.error("---Exception occurred---")
return super(RushesViewSet, self).get_serializer_class()
- [Django]-How do you change the collation type for a MySQL column?
- [Django]-Django 1.3.1 compilemessages. Error: sh: msgfmt: command not found
- [Django]-Data Mining in a Django/Postgres application