Access different serializer if a queryset is empty Django Rest Framework - django

I don't think I am implementing this correctly, but I am trying to change the serializer used for a queryset based on a condition (if there are no venues in one queryset, switch to another serializer and just return list object). I'm not quite sure how to do this.
Here is the view
class SavedVenuesViewSet(viewsets.ModelViewSet):
serializer_class = UserVenueSerializer
def get_queryset(self):
list_id = self.request.GET.get('list_id', None)
user = self.request.user.id
print(user)
print(list_id)
print(type(list_id))
qs = UserVenue.objects.filter(user_list=int(float(list_id)))
if not qs:
print("EMPTY LIST") #this is where i try to switch serializer
serializer_class = UserListSerializer
return UserVenue.objects.filter(id=int(float(list_id)))
else:
return qs
Here are the relevant serializers:
class UserVenueSerializer(serializers.ModelSerializer):
venue = mapCafesSerializer()
class Meta:
model = UserVenue
fields = ['user', 'user_list', 'venue']
depth = 2
[...]
class UserListSerializer(serializers.ModelSerializer):
class Meta:
model = UserList
fields = ['id', 'user', 'list_name']
depth = 2
The traceback isn't throwing an error but it isn't doing what I am hoping:
1
45
<class 'str'>
EMPTY LIST
[29/Sep/2021 11:05:36] "GET /api/savedvenues/?list_id=45 HTTP/1.1" 200 2

This is the correct practice to change serializer class in ModelViewSet:
You have to override get_serializesr_class method:
class SavedVenuesViewSet(viewsets.ModelViewSet):
serializer_class = UserVenueSerializer
def get_serializer_class(self):
if not self.get_queryset(): # Check your conditions here
return UserListSerializer
else:
return UserVenueSerializer

You could remove serializer_class field from your view, and create get_serializer_class() method, that will contain the logic for choosing the serializer. Is the best practice.
But, you can also, do it in your get queryset.
First, remove serializer_class attribute from your view. Instead, set it inside your get_queryset method (with this.serializer_class);
def get_queryset(self):
list_id = self.request.GET.get('list_id', None)
qs = UserVenue.objects.filter(user_list=int(float(list_id)))
if not qs:
self.serializer_class = UserVenueSerializer
return UserVenue.objects.filter(id=int(float(list_id)))
else:
self.serializer_class = UserListSerializer
return qs
Don't forget use the self.serializer_class.

Related

Django REST Framework API request with bad parameters returns all objects

When someone requests:
127.0.0.1:8000/api/current_metadata?SOME_FIELD_THAT_DOESNT_EXIST=1
this code returns everything. I want it to return nothing. Some kind of strict setting?
class GetCrtMetadata(generics.ListAPIView):
permission_classes=[IsAuthenticated]
serializer_class = CurrentDeploymentSerializer
queryset=deployment.objects.all()
filter_backends = [DjangoFilterBackend]
filter_fields = [field.name for field in deployment._meta.fields]
Your invalid params don't make the filter works, so if you want to return empty, you should validate the query params:
class GetCrtMetadata(generics.ListAPIView):
...
def is_valid_params(self):
# Do something to validate the params
def get_queryset(self):
"""
Cusotm get queryset
"""
queryset = super().get_queryset()
if self.action == 'list' and not self.is_valid_params():
return queryset.none()
return queryset

How to return specific field queryset DRF

I want to create a custom queryset class that returns different fields to pre-define two cases.
when DateField is greater than today
when it's less than today.
In case it's greater return all fields, else return only date_to_open and post_name fields.
views.py
class GroupDetail(generics.RetrieveAPIView):
serializer_class = serializers.GroupDetailsSerializer
permission_classes = (IsAuthenticated, )
def greater(self):
return models.Group.objects.filter(shared_to=self.request.user,
date_to_open__gt=timezone.now()).exists()
def get_queryset(self, *args, **kwargs):
if self.greater():
query_set = models.Group.objects.filter(shared_to=self.request.user,
date_to_open__gt=timezone.now())
else:
query_set = SPECIFIC FIELDS
return query_set
serializers.py
class GroupDetailsSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.name')
images = GroupImageSerializer(many=True, read_only=True)
shared_to = serializers.SlugRelatedField(queryset=models.UserProfile.objects.all(),
slug_field='name', many=True)
class Meta:
model = models.Group
fields = ('id', 'group_name', 'owner', 'group_text', 'created_on', 'date_to_open', 'shared_to',
'images', )
Ok. Thanks to #ArakkalAbu comment I've just overridden get_serializer_class()
views.py
class GroupDetail(generics.RetrieveAPIView):
queryset = models.Group.objects.all()
serializer_class = serializers.GroupDetailsSerializer
permission_classes = (IsAuthenticated, )
def greater(self):
return models.Group.objects.filter(shared_to=self.request.user, date_to_open__gt=timezone.now()).exists()
def get_serializer_class(self):
if self.greater():
return serializers.GroupDetailsSerializer
else:
return serializers.ClosedGroupDetailsSerializer
You can keep using the same logic and use values_list to return specific values out of the Query set. The returned values is also a query set
def get_queryset(self, *args, **kwargs):
if self.greater():
return models.Group.objects.filter(shared_to=self.request.user, date_to_open__gt=timezone.now())
else:
return models.Group.objects.filter(shared_to=self.request.user, date_to_open__lt=timezone.now()).values_list('date_to_open', 'post_name' , flat = True)

Django get_queryset returns 500 instead of 404

Here is my view:
class SampleViewSet(viewsets.ModelViewSet):
serializer_class = SampleSerializer
queryset = Sample.objects.all()
def get_queryset(self):
queryset = self.queryset
test_code = self.request.query_params.get('test_code', None)
if test_code is not None:
queryset = queryset.filter(test__test_code=test_code)
return queryset
Here is my Model:
class Sampe(models.Model):
test = models.OneToOneField(Test, null=True, blank=True, on_delete=models.CASCADE)
# few more fields- not so important
Here is my serializer:
class SampleSerializer(serializers.ModelSerializer):
test_code = serializers.CharField(source='test.test_code')
class Meta:
model = Sample
fields = '__all__'
This works when I hit /api/sample?test_code="existing_param"
So when I do /api/sample?test_code="Not_Existing_param", I was expecting it should throw me 404 instead, it throws 500 Test matching query does not exist. Really appreciate any help.
Thank you
Well the problem is that the function will raise an error, and the web server can not really interpret what caused this error, so it is more accurate to raise a 500 in general, than a 404.
But what you can do is use a get_list_or_404 which acts like a get_object_or_404, but then with a .filter(..) instead of a .get(..):
from django.shortcuts import get_list_or_404
class SampleViewSet(viewsets.ModelViewSet):
serializer_class = SampleSerializer
queryset = Sample.objects.all()
def get_queryset(self):
queryset = self.queryset
test_code = self.request.query_params.get('test_code', None)
if test_code is not None:
queryset = get_list_or_404(queryset, test__test_code=test_code)
return queryset
This will thus filter the queryset, and raise a 404 exception in case the resulting queryset turns out to be empty. In case you chain filters together, it is sufficient to do this on the last element, since then you inspect the actual queryset you will return, and thus avoid checking multiple quersets that are not used later in the process.

Pass request context to serializer from Viewset in Django Rest Framework

I have a case where the values for a serializer field depend on the identity of the currently logged in user. I have seen how to add the user to the context when initializing a serializer, but I am not sure how to do this when using a ViewSet, as you only supply the serializer class and not the actual serializer instance.
Basically I would like to know how to go from:
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
to:
class myModelSerializer(serializers.ModelSerializer):
uploaded_by = serializers.SerializerMethodField()
special_field = serializers.SerializerMethodField()
class Meta:
model = myModel
def get_special_field(self, obj):
if self.context['request'].user.has_perm('something.add_something'):
return something
Sorry if it wasn't clear, from the DOCs:
Adding Extra Context
Which says to do
serializer = AccountSerializer(account, context={'request': request})
serializer.data
But I am not sure how to do that automatically from the viewset, as I only can change the serializer class, and not the serializer instance itself.
GenericViewSet has the get_serializer_context method which will let you update context:
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = MyModelSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({"request": self.request})
return context
For Python 2.7, use context = super(MyModelViewSet, self).get_serializer_context()
For Function based views you can pass request or user as follows:
serializer = ProductSerializer(context = {"request": request}, data=request.data)
Your Serializer may look like:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id"]
def create(self, validated_data):
user = self.context["request"].user
print(f"User is: {user}")
Feel free to inform if there is any better way to do this.
just use get_serializer() in your viewsets
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Return parent context in overrided function get_serializer_context will make it easy to access request and its data.
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
def get_serializer_context(self):
"""
pass request attribute to serializer
"""
context = super(myModelViewSet, self).get_serializer_context()
return context
This is very stable as every time we request viewset, it returns context as well.
the values for a serializer field depend on the identity of the currently logged in user
This is how I handle such cases in my ModelViewSet:
def perform_create(self, serializer):
user = self.request.user
if user.username == 'myuser':
serializer.data['myfield'] = 'something'
serializer.save()
Simply add this 2 line method in your class and you are good to go.
def get_serializer_context(self):
return {'request': self.request}
since the posted answers had partial correctness, summarizing here in the interest of completeness.
override get_serializer_context..AND
use get_serializer in your views instead of manually calling the serializer

How do I set different Serializer for list and detail view with Django Rest Framework?

How do I set a different Serializer for list and detail view while using viewsets.ModelViewSet and HyperlinkedSerializer?
I see how to do it with viewsets.ViewSet by defining list and retrive, (here's an example) but I don't know how to elegantly adapt it to viewsets.ModelViewSet
I've adapted an answer from "Django rest framework, use different serializers in the same ModelViewSet" that serves me very well, and I hope you'll find useful:
class MyModelViewSet(viewsets.MyModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelListSerializer
detail_serializer_class = MyModelDetailSerializer
def get_serializer_class(self):
if self.action == 'retrieve':
if hasattr(self, 'detail_serializer_class'):
return self.detail_serializer_class
return super(MyModelViewSet, self).get_serializer_class()
In this case, you're just specifying your two serializers and using the one depending on the action. However, this can be made more general (for all actions) as follows:
class MyModelViewSet(viewsets.MyModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
action_serializers = {
'retrieve': MyModelDetailSerializer,
'list': MyModelListSerializer,
'create': MyModelCreateSerializer
}
def get_serializer_class(self):
if hasattr(self, 'action_serializers'):
return self.action_serializers.get(self.action, self.serializer_class)
return super(MyModelViewSet, self).get_serializer_class()
Viewsets extend the class GenericAPIView, so you can use this part of the documentation to solve your problem. Basically, what you need is to override get_serializer_class and to return a different serializer based on your request.
I've created this small package for this job. drf_custom_viewsets.
It has CustomSerializerViewSet, which inherits from ModelViewSet, which lets you set different serializers for different actions.
As of 2021, I will do it differently, a better and more generic way around is doing something like this:
class PlayersListViewSet(viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayersListSerializer
http_method_names = ['get', 'post']
pagination_class = None
filter_backends = [filters.OrderingFilter]
ordering_fields = ['name']
serializer_class_by_action = {
'retrieve': PlayersDetailSerializer,
'list': PlayersListSerializer,
}
def get_serializer_class(self):
if hasattr(self, 'serializer_class_by_action'):
return self.serializer_class_by_action.get(self.action, self.serializer_class)
return super(MyModelViewSet, self).get_serializer_class()
def get_queryset(self):
queryset = Player.objects.all()
team_id = self.request.query_params.get('team', None)
if team_id:
try:
queryset = queryset.filter(team=team_id)
except ValueError:
raise exceptions.ParseError()
return queryset
Here action is the method used by the serializer, list in case of def list, retrieve in case of def retrieve and so on..
Thanks to #bbengfort, I've provided a simple solution without the need to create a new ViewSet.
Different Serializer Per Action In the ViewSet in Django
TL;DR
In the following code, we are overriding get_serializer_class based on Django documentation and we're specifying different serializers per action if it's needed:
Views.py
class TestAPIView(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = TestAPISerializer
serializer_class_by_action = {
'update_me': UpdateMeSerializer,
}
def get_serializer_class(self):
if hasattr(self, 'serializer_class_by_action'):
return self.serializer_class_by_action.get(self.action, self.serializer_class)
return self.serializer_class
#action(detail=True, methods=['patch'], url_name='Update Me', url_path='updateme')
def update_me(self, request, pk=None):
# Write your own logic
return Response("OK")
Serializers.py
class UpdateMeSerializer(serializers.Serializer):
count = serializers.CharField(required=False, allow_null=True, default=10)
class Meta:
fields = ['count']