22👍
For anyone else stumbling across this, I found overriding get_serializer_class on the viewset and defining a serializer per action was the DRY-est option (keeping a single viewset but allowing for dynamic serializer choice):
class MyViewset(viewsets.ModelViewSet):
serializer_class = serializers.ListSerializer
permission_classes = [permissions.IsAdminUser]
renderer_classes = (renderers.AdminRenderer,)
queryset = models.MyModel.objects.all().order_by('-updated')
def __init__(self, *args, **kwargs):
super(MyViewset, self).__init__(*args, **kwargs)
self.serializer_action_classes = {
'list':serializers.AdminListSerializer,
'create':serializers.AdminCreateSerializer,
'retrieve':serializers.AdminRetrieveSerializer,
'update':serializers.AdminUpdateSerializer,
'partial_update':serializers.AdminUpdateSerializer,
'destroy':serializers.AdminRetrieveSerializer,
}
def get_serializer_class(self, *args, **kwargs):
"""Instantiate the list of serializers per action from class attribute (must be defined)."""
kwargs['partial'] = True
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(MyViewset, self).get_serializer_class()
Hope this helps someone else.
10👍
You can override the ‘get_fields’ method your serializer class and to add something like that:
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
request = self.context.get('request')
if request is not None and not request.parser_context.get('kwargs'):
fields.pop('your_field', None)
return fields
In this case when you get detail-view there is ‘kwargs’: {‘pk’: 404} and when you get list-view there is ‘kwargs’: {}
- [Django]-How do you configure Django for simple development and deployment?
- [Django]-Attempt to write a readonly database – Django w/ SELinux error
- [Django]-Why does django's prefetch_related() only work with all() and not filter()?
9👍
I wrote an extension called drf-action-serializer
(pypi) that adds a serializer called ModelActionSerializer
that allows you to define fields/exclude/extra_kwargs on a per-action basis (while still having the normal fields/exclude/extra_kwargs to fall back on).
The implementation is nice because you don’t have to override your ViewSet get_serializer
method because you’re only using a single serializer. The relevant change is that in the get_fields
and get_extra_kwargs
methods of the serializer, it inspects the view action and if that action is present in the Meta.action_fields
dictionary, then it uses that configuration rather than the Meta.fields
property.
In your example, you would do this:
from action_serializer import ModelActionSerializer
class PersonSerializer(ModelActionSerializer):
class Meta:
model = Person
fields = ('first_name', 'last_name', 'nickname', 'slug', 'address')
action_fields = {
'list': {'fields': ('nickname', 'slug')}
}
Your ViewSet would look something like:
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
And your router would look normal, too:
router = routers.DefaultRouter()
router.register(r'persons', api.PersonViewSet)
Implementation
If you’re curious how I implemented this:
- I added a helper method called
get_action_config
which gets the current view action and returns that entry in theaction_fields
dict:
def get_action_config(self):
"""
Return the configuration in the `Meta.action_fields` dictionary for this
view's action.
"""
view = getattr(self, 'context', {}).get('view', None)
action = getattr(view, 'action', None)
action_fields = getattr(self.Meta, 'action_fields', {})
- I changed
get_field_names
ofModelSerializer
:
From:
fields = getattr(self.Meta, 'fields', None)
exclude = getattr(self.Meta, 'exclude', None)
To:
action_config = self.get_action_config()
if action_config:
fields = action_config.get('fields', None)
exclude = action_config.get('exclude', None)
else:
fields = getattr(self.Meta, 'fields', None)
exclude = getattr(self.Meta, 'exclude', None)
- Finally, I changed the
get_extra_kwargs
method:
From:
extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {}))
To:
action_config = self.get_action_config()
if action_config:
extra_kwargs = copy.deepcopy(action_config.get('extra_kwargs', {}))
else:
extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {}))
- [Django]-What is the best way to upload files in a modern browser
- [Django]-Django template convert to string
- [Django]-Django ModelForm for Many-to-Many fields
2👍
If you want to change what fields are displayed in the List vs Detail view, the only thing you can do is change the Serializer used. There’s no field that I know of that lets you specify which fields of the Serializer gets used.
- [Django]-Django – [Errno 111] Connection refused
- [Django]-Unable to find a locale path to store translations for file __init__.py
- [Django]-How to check if a user is logged in (how to properly use user.is_authenticated)?
1👍
The field selection on you serializers should be working, but I don’t know what might be happening exactly. I have two solutions you can try:
1 Try to change the way you declare you serializer object
#If you aren't using Response:
from rest_framework.response import Response
class PersonListViewSet(viewsets.ModelViewSet):
def get(self, request):
queryset = Person.objects.all()
serializer_class = PersonListSerializer(queryset, many=True) #It may change the things
return Response(serializer_class.data)
class PersonViewSet(viewsets.ModelViewSet):
def get(self, request, pk): #specify the method is cool
queryset = Person.objects.all()
serializer_class = PersonSerializer(queryset, many=True) #Here as well
#return Response(serializer_class.data)
2 The second way around would change your serializers
This is not the most normal way, since the field selector should be working but you can try:
class PersonListSerializer(serializers.ModelSerializer):
nickname = serializers.SerializerMethodField() #Will get the attribute my the var name
slug = serializers.SerializerMethodField()
class Meta:
model = Person
def get_nickname(self, person):
#This kind of method should be like get_<fieldYouWantToGet>()
return person.nickname
def get_slug(self, person):
#This kind of method should be like get_<fieldYouWantToGet>()
return person.slug
I hope it helps. Try to see the APIview class for building your view too.
- [Django]-South migration: "database backend does not accept 0 as a value for AutoField" (mysql)
- [Django]-How do I require an inline in the Django Admin?
- [Django]-Django: Purpose of django.utils.functional.SimpleLazyObject?
0👍
Somehow close:
If you just want to skip fields in the serilaizer
class UserSerializer(serializers.ModelSerializer):
user_messages = serializers.SerializerMethodField()
def get_user_messages(self, obj):
if self.context.get('request').user != obj:
# do somthing here check any value from the request:
# skip others msg
return
# continue with your code
return SystemMessageController.objects.filter(user=obj, read=False)
- [Django]-Django best way to check the model type of a queryset
- [Django]-How to have two models reference each other Django
- [Django]-Exception: You cannot access body after reading from request's data stream
0👍
I rewrite ModelViewSet list function to modify serializer_class.Meta.fields attribute, code like this:
class ArticleBaseViewSet(BaseViewSet):
def list(self, request, *args, **kwargs):
exclude = ["content"]
self.serializer_class.Meta.fields = [f.name for f in self.serializer_class.Meta.model._meta.fields if f.name not in exclude]
queryset = self.filter_queryset(self.get_queryset()).filter(is_show=True, is_check=True)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class BannerArticleViewSet(ArticleBaseViewSet):
queryset = BannerArticle.objects.filter(is_show=True, is_check=True).all()
serializer_class = BannerArticleSerializer
permission_classes = (permissions.AllowAny,)
But it looks not stable, so i will not use it, just share to figure out the best way
- [Django]-Django – use reverse url mapping in settings
- [Django]-How to debug gunicorn failure issues? (Worker failed to boot)
- [Django]-Django return HttpResponseRedirect to an url with a parameter
0👍
My solution.
class BaseSerializerMixin(_ModelSerializer):
class Meta:
exclude: tuple[str, ...] = ()
exclude_in_list: tuple[str, ...] = ()
model: Type[_models.Model]
def get_action(self) -> Optional[str]:
if 'request' not in self.context:
return None
return self.context['request'].parser_context['view'].action
def get_fields(self):
fields = super().get_fields()
if self.get_action() == 'list':
[fields.pop(i) for i in list(fields) if i in self.Meta.exclude_in_list]
return fields
- [Django]-Django import error – no module named django.conf.urls.defaults
- [Django]-InvalidBasesError: Cannot resolve bases for [<ModelState: 'users.GroupProxy'>]
- [Django]-Foreign key from one app into another in Django
-1👍
I think it should be like this:
router.register(r'persons/?P<slug>/', api.PersonViewSet)
and you should include a line like this:
lookup_field='slug'
in your serializer class. Like this:
class PersonSerializer(serializers.ModelSerializer):
lookup_field='slug'
class Meta:
model = Person
fields = ('first_name', 'last_name', 'nickname', 'slug', 'address')
- [Django]-A field with precision 10, scale 2 must round to an absolute value less than 10^8
- [Django]-Django — User.DoesNotExist does not exist?
- [Django]-Remove "add another" in Django admin screen