Conditional fields serializer Django - django

I have a view like this:
class UserDetail(generics.RetrieveDestroyAPIView):
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = User.object.all()
serializer_class = UserSerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('pk')
return generics.get_object_or_404(User, id=item)
serializer like this:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'city']
and urls like this:
path('<uuid:pk>', UserDetail.as_view(), name='user_detail')
Can I using just one view and one serializer fetch in one case all data (id, fist_name, last_name and city) and in other case just the city by json? Or maybe I have to create for it especially a new view and serializer like this:
class UserCity(generics.RetrieveDestroyAPIView):
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = User.object.all()
serializer_class = UserJustCitySerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('pk')
return generics.get_object_or_404(User, id=item)
and
class UserJustCitySerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['city']

You can try SerializerMethodField. Which will return the field if the condition is true inside your get_your_conditional_field method.
class YourSerializer(serializers.ModelSerializer):
your_conditional_field_name = serializers.SerializerMethodField()
class Meta:
model = model_name
def get_your_conditional_field(self, obj):
# do your conditional logic here
# and return appropriate result
return obj

Related

Django check if object in post method have specific value

I'm using DRF and have object with ManyToMany field. I'd like to check if object that user sent on server contains any pk in that field. Then i want to set boolean field to true in linked object to that ManyToMany.
Models:
class Parent(models.Model):
child_link = models.ManyToManyField(child, related_name="child")
class Child(models.Model):
in_use = models.BooleanField(default=False)
Views:
class ParentView(viewsets.ModelViewSet):
serializer_class = ParentSerializer
authentication_classes = (SessionAuthentication, )
def get_queryset(self):
user = self.request.user
return Parent.objects.filter(user=user)
class ChildView(viewsets.ModelViewSet):
serializer_class = ChildSerializer
authentication_classes = (SessionAuthentication, )
def get_queryset(self):
user = self.request.user
return Child.objects.filter(user=user)
Serializers:
class ParentSerializer(serializers.ModelSerializer):
class Meta:
model = Parent
fields = __all__
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = Child
fields = __all__
In this specific case i described, the best solution is to add create() to parent serializer:
class ParentSerializer(serializers.ModelSerializer):
def create(self, *args, **kwargs):
if len(self.data["child_link"]) != 0:
for id in self.data["child_link"]:
obj = Child.objects.get(pk=id)
obj.in_use = True
obj.save()
return super().create(*args, **kwargs)
class Meta:
model = Parent
fields = __all__

Update field without rest API

I have a model.py
class UserPaymentInformation(models.Model):
...
awaiting_confirmation = models.BooleanField(default=False)
I want to make awaiting_confirmation = True in code. But forbid awaiting_confirmation update via RestAPI call.
views.py
class UserPaymentInformationUpdateAPIView(generics.UpdateAPIView):
permission_classes = (IsAuthenticatedDriver,)
serializer_class = UserPaymentInformationUpdateSerializer
queryset = serializer_class.Meta.model.objects.all()
def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
instance = self.serializer_class.Meta.model.objects.get(
user=self.request.user
)
self.mark_user_as_new()
# awaiting_confirmation = True # I WANT SOMETHING LIKE THIS
serializer = self.get_serializer(
instance, data=request.data, partial=partial
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({"result": serializer.data})
serializers.py
class UserPaymentInformationUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = UserPaymentInformation
fields = ("id", "full_name", "card_number", "account_number", "bik", "awaiting_confirmation")
How can I fix update method?
In your UserPaymentInformationUpdateSerializer you could set the read_only_fields = ('awaiting_confirmation',), so your serializer would become:
class UserPaymentInformationUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = UserPaymentInformation
fields = ("id", "full_name", "card_number", "account_number", "bik", "awaiting_confirmation")
read_only_fields = ("awaiting_confirmation",)
This would mean it'd still be returned in the serializer data but it would not be possible to update it through an API request.

Django Rest Framework SerializerMethodField only on GET Request

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)

Changing Return Type on Creation in Serializer

I have the following two serializers:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
from radio.models import Program
model = Program
fields = ('id', 'title')
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program', 'program_data',)
They are based on the following models:
class Program(models.Model):
title = models.CharField(max_length=64)
class UserRecentlyPlayed(models.Model):
user = models.ForeignKey(User)
program = models.ForeignKey(Program)
What I'm trying to do is the following: On create, I want to be able create a new instance of UserRecentlyPlayed in the following manner:
{
"user": "...user id ....",
"program": "....program id...."
}
However, when I return a list, I would like to return the following:
[{
"id": "... id .... ",
"user": ".... user id .....",
"program": {"id": "...program id...", "title": "...title..." }
}]
These are called in the following view:
class RecentlyPlayed(generics.ListCreateAPIView):
serializer_class = UserRecentlyPlayedSerializer
This, unfortunately is not working. What is the correct magic for this?
You can rename your program_data in your serializer to program or you can specify source for your nested serializer.
That should return the output of list as you'd like.
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program',)
or
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True, source='program')
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program_data',)
And to support same json input for create, the easiest way is create another serializer for input:
class UserRecentlyPlayedSerializerInput(serializers.ModelSerializer):
program = serializers.PrimaryKeyRelatedField(queryset=Program.objects.all())
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program',)
And use it in your view when request is POST/PUT/PATCH:
class RecentlyPlayed(generics.ListCreateAPIView):
serializer_class = UserRecentlyPlayedSerializer
def get_serializer_class(self):
if self.request.method.lower() == 'get':
return self.serializer_class
return UserRecentlyPlayedSerializerInput
While this works great for a "get", I would like to see that same
result after a create. I still see {"program": "...id...."
For this, you have to change slightly the implementation of create method in your view
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
headers = self.get_success_headers(serializer.data)
oser = UserRecentlyPlayedSerializer(instance)
return Response(oser.data, status=status.HTTP_201_CREATED,
headers=headers)
Firstly create a property named program_data in your model
class Program(models.Model):
title = models.CharField(max_length=64)
class UserRecentlyPlayed(models.Model):
user = models.ForeignKey(User)
program = models.ForeignKey(Program)
#property
def program_data(self):
return self.program
Then in your serializer you do not need to change anything following, it will remain same as below
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
from radio.models import Program
model = Program
fields = ('id', 'title')
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program', 'program_data',)
Ok, I went in a slightly different direction and it works. Instead of using the ListCreateAPIView, I created my own class using ListModeMixin, CreateModelMixin and GenericAPIView. The magic was in overriding the def list class. I also implemented a "return_serializer_class" attribute. That's what did it.
class RecentlyPlayed(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
serializer_class = UserRecentlyPlayedSerializer
return_serializer_class = ProgramSerializer
parser_classes = (JSONParser, MultiPartParser)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.create(request, *args, **kwargs)
return self.list(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
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.return_serializer_class(queryset, many=True)
return Response({'recently_played': serializer.data})

Return nested serializer in serializer method field

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'}