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)
Related
Does ModelSerializer have an option to change the fields dynamically by GET or (POST, PUT, DELETE)?
While GET requires complex fields such as nested serializers, these are not required for (POST, PUT, DELETE).
I think the solution is to use separate serializers for GET and (POST, PUT, DELETE).
But in that case, I'd have to create quite a few useless serializers.
Is there any good solution?
class PlaylistSerializer(serializers.ModelSerializer):
user = UserDetailSerializer(read_only=True)
tracks = serializers.SerializerMethodField()
is_owner = serializers.SerializerMethodField()
is_added = serializers.SerializerMethodField()
is_favorited = serializers.BooleanField()
class Meta:
model = Playlist
fields = (
"pk",
"user",
"title",
"views",
"is_public",
"is_wl",
"created_at",
"updated_at",
"tracks",
"is_owner",
"is_added",
"is_favorited",
)
def get_is_owner(self, obj):
return obj.user == self.context["request"].user
def get_tracks(self, obj):
queryset = obj.track_set
if queryset.exists():
tracks = TrackSerializer(queryset, context=self.context, many=True).data
return tracks
else:
return []
def get_is_added(self, obj):
try:
return obj.is_added
except AttributeError:
return False
class PlaylistUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
fields = ("title", "is_public")
first you need to create a class and inherit your serializer from this class as below:
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""To be used alongside DRF's serializers.ModelSerializer"""
#classmethod
def default_fieldset(cls):
return cls.Meta.fields
def __init__(self, *args, **kwargs):
self.requested_fields = self._extract_fieldset(**kwargs)
# Fields should be popped otherwise next line complains about
unexpected kwarg
kwargs.pop('fields', None)
super().__init__(*args, **kwargs)
self._limit_fields(self.requested_fields)
def _extract_fieldset(self, **kwargs):
requested_fields = kwargs.pop('fields', None)
if requested_fields is not None:
return requested_fields
context = kwargs.pop('context', None)
if context is None:
return None
return context.get('fields')
def _limit_fields(self, allowed_fields=None):
if allowed_fields is None:
to_exclude = set(self.fields.keys()) - set(self.default_fieldset())
else:
to_exclude = set(self.fields.keys()) - set(allowed_fields)
for field_name in to_exclude or []:
self.fields.pop(field_name)
#classmethod
def all_fields_minus(cls, *removed_fields):
return set(cls.Meta.fields) - set(removed_fields)
then your serializer would be something like this:
class PlaylistSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Playlist
fields = ("pk", "user", "title", "views", "is_public",
"is_wl", "created_at", "updated_at", "tracks", "is_owner",
"is_added", "is_favorited",)
#classmethod
def update_serializer(cls):
return ("title", "is_public")
#classmethod
def view_serializer(cls):
return ("title", "is_public", "is_owner", "is_added")
then you will call your serializer as below:
PlaylistSerializer(instance, fields=PlaylistSerializer.update_serializer()).data
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.
I need to increase the view count with 1 on each refresh. I'm not sure about the method since I'm new to DRF. Thanks in advance.
models.py
class Module(models.Model):
name = models.CharField(max_length=250, default="")
view_count = models.IntegerField(default=0)
serializers.py
class ModuleSerializer(serializers.ModelSerializer):
class Meta:
model = Module
fields = "__all__"
views.py
class ModuleView(generics.ListAPIView):
queryset = Module.objects.all()
serializer_class = ModuleSerializer
def get(self, request):
obj = self.get_object
print(obj)
obj.view_count = obj.view_count + 1
obj.save(view_count="view_count")
return super().get(request)
i implemented view count with F expression because a view count is basicly a race condidition and the docs clearly state that
Avoiding race conditions using F()
Documentation
class ElonDetail(generics.RetrieveAPIView):
queryset = Elon.objects.all()
serializer_class = ElonDetailSerializer
lookup_field = 'slug'
#retrieve
def retrieve(self, request, *args, **kwargs):
obj = self.get_object()
print(obj)
obj.view = obj.view + 1
obj.save(update_fields=['view',])
serializer = self.get_serializer(obj)
return Response(serializer.data, status=200)
here i used RetrieveAPIView
Running into a little snag here with my DRF backend.
I am populating fields with choices on certain models.
I have a foreign key requirement on one model. When I create the model I want to save it under the foreign id.
When I request the models, I want the model with whatever the choice field maps to.
I was able to do this with SerializerMethodField, however when I try to create a model, I get a 400 error because the block is not valid. If I remove the SerializerMethodField, I can save, but get the number stored in the db from the request.
Any help would be appreciated.
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = serializers.BlockSerializer(data=data, context={'request': request})
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
class WorkoutGoalSerializer(serializers.ModelSerializer):
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
The above code returns the correct choice, but I can't save under it. Remove the goal = WorkoutGoalSerializer() and it saves but doesn't return the mapped choice.
I think this will work like a charm,
class WorkoutGoalSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' in self.context and self.context['request'].method == 'GET':
self.fields['goal'] = serializers.SerializerMethodField(read_only=True, source='get_goal')
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal') # remove this line
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
How this Work?
It will re-initiate the goal field with SerializerMethodField, if the reuested method is GET.
Remember one thing, you should remove the line,
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
serializers.py
class BlockCreateSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
views.py
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def get_serializer_class(self):
if self.action == 'create':
return serializers.BlockCreateSerializer
else:
return self.serializer_class
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = self.get_serializer(data=data)
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
override get_serializer_class to return different serializer_class for create and other action(list\retrieve\update\partial_update)
I have a method field called followers. I get the list of followers in a SerializerMethodField :
followers = serializers.SerializerMethodField()
I want to format the result with a specific serializer called BaseUserSmallSerializer. How should I implement the method get_followers to achieve that ?
Try this;
followers = BaseUserSmallSerializer(source='get_followers', many=True)
OR
You can use serializer inside methodfield;
def get_followers(self, obj):
followers_queryset = #get queryset of followers
return BaseUserSmallSerializer(followers_queryset, many=True).data
If you prefer a more generic solution:
SerializerMethodNestedSerializer which works same as serializers.SerializerMethodField but wraps the result with the passed serializer and returns a dict
class SerializerMethodNestedSerializer(serializers.SerializerMethodField):
"""Returns nested serializer in serializer method field"""
def __init__(self, kls, kls_kwargs=None, **kwargs):
self.kls = kls
self.kls_kwargs = kls_kwargs or {}
super(SerializerMethodNestedSerializer, self).__init__(**kwargs)
def to_representation(self, value):
repr_value = super(SerializerMethodNestedSerializer, self).to_representation(value)
if repr_value is not None:
return self.kls(repr_value, **self.kls_kwargs).data
Usage
class SomeSerializer(serializers.ModelSerializer):
payment_method = SerializerMethodNestedSerializer(kls=PaymentCardSerializer)
def get_payment_method(self, obj):
return PaymentCard.objects.filter(user=obj.user, active=True).first()
class Meta:
model = Profile
fields = ("payment_method",)
class PaymentCardSerializer(serializers.ModelSerializer):
class Meta:
fields = ('date_created', 'provider', 'external_id',)
model = PaymentCard
The expected output of SerializerMethodNestedSerializer(kls=PaymentCardSerializer)
None or {'date_created': '2020-08-31', 'provider': 4, 'external_id': '123'}