Django REST - hide deserialized data - python

I would like to store some data in one of my database field. The data is added to that field while deserialization with POST method. Later when I want to show data with GET method I don't want that one field to be presented.
When I do POST I deserialize that string:
{
"car_id": 3,
"rating": 3
}
Later in views.py I do the deserialization while POST:
#api_view(['POST'])
def car_rate(request):
if request.method == 'POST':
rate_data = JSONParser().parse(request)
rate_serializer = CarRateSerializer(data=rate_data)
if rate_serializer.is_valid():
try:
car_obj = Car.objects.get(pk=rate_data['car_id'])
except Car.DoesNotExist:
return JsonResponse({'message': 'The car with given ID does not exist!'}, status=status.HTTP_404_NOT_FOUND)
# check if rate is from 1 to 5
r = rate_serializer.validated_data['rating']
if int(r) >= 1 and int(r) <= 5:
rate_serializer.save()
return JsonResponse({'message':'The rate is in the scope!'})
else:
return JsonResponse({'message':'The rate is NOT in the scope!'})
return JsonResponse(rate_serializer.errors)
And there is my models.py:
class Car(models.Model):
make = models.CharField(max_length=15)
model = models.CharField(max_length=15)
avg_rating = models.FloatField(default=0)
def __str__(self): # print it when Car instance is needed
return self.make
class CarRate(models.Model):
car_id = models.ForeignKey(Car, related_name='rates',
on_delete=models.CASCADE,
default=0)
rating = models.PositiveIntegerField(default=0)
The code does works (somehow). For now there can be added rates for one car (multiple rates) with POST moethods. I store the rates in CarRate class and later it will be used to calculate the average rate for a car. I just simply don't want to print it out with GET.
This is my output right now:
{
"id": 2,
"make": "Volkswagen",
"model": "Golf",
"rates": [
4,
4,
2,
3
],
"avg_rating": 0.0
},
I simply want the rates field to be invisible while printing.
I read about defer() method and tried it out, but nothing happened. Any help?

If you absolutely don't want that field to be in your database ever, then you can simply remove that field from the Serializer field option (You named that CarRateSerializer)
But if you want that to be in your database but you don't want that to show as output, you can use extra_kwargs with 'write_only': True in your serializer class. I'm giving you an example I used for one of my projects
class TopicSerializer(serializers.ModelSerializer):
class Meta:
model = Topic
fields = ['id','title', 'totalMarks', 'status', 'categoryID']
extra_kwargs = {'categoryID': {'write_only': True}}
for your code, you can add this line of code below fields in that class Meta of your CarRateSerializer
extra_kwargs = {'rating': {'write_only': True}}
I hope this should solve your issue

Just remove rating field from, CarRateSerializer OR you can create a new Serializer for CarRate.

Related

How to properly update a many to many nested serializer?

I have been able to replicate the create method to add the correct nested serializers in a POST request. However, I'm still having issues updating in a PUT or PATCH. When using a PUT or PATCH request and I pass the entire object data or the "brands" data, it will only update in the position it is passed. So if I have an object with 3 values:
"brands": [
{
"id": 1,
"name": "Brand 1 Test"
},
{
"id": 2,
"name": "Brand 2 Test"
},
{
"id": 3,
"name": "Brand 3 Test"
}
}
If I pass:
"brands": [
{
"id": 1,
"name": "Brand 1 Test"
},
{
"id": 2,
"name": "Brand 2 Test"
}
It will give me the same list of 3 brands. But if I do that in reverse order it will update and add the 3rd brand. I'm not sure what's causing it. Here's the code I have:
Models
class Brand(models.Model):
name = models.CharField(max_length=500)
class Incentive(models.Model):
name = models.CharField(max_length=500)
brands = models.ManyToManyField(Brand, related_name='incentives_brand')
start_dt = models.DateTimeField(auto_now_add=False, blank=True, null=True)
end_dt = models.DateTimeField(auto_now_add=False, blank=True, null=True)
Serializers
class BrandSerializer(serializers.ModelSerializer):
class Meta:
model = Brand
depth = 1
fields = ['id', 'name']
class IncentiveSerializer(serializers.ModelSerializer):
brands = BrandSerializer(many=True)
class Meta:
model = Incentive
fields = ['id', 'name', 'brands', 'start_dt', 'end_dt']
def create(self, validated_data):
brands = validated_data.pop('brands', [])
instance = Incentive.objects.create(**validated_data)
for brand_data in brands:
brand = Brand.objects.get(**brand_data)
instance.brands.add(brand)
return instance
def update(self, instance, validated_data):
brands = validated_data.pop('brands', [])
instance = super().update(instance, validated_data)
for brand_data in brands:
brand = Brand.objects.get(**brand_data)
instance.brands.add(brand)
return instance
I think the issue lies somewhere here. If any more code is needed please let me know(ex. views, urls). I'm guessing in the update I'm not properly emptying the list of brands. I just can't see it. Any help would be appreciated.
I think the clue here is that you do instance.brands.add, which does exactly that, adding. Not removing as you noticed :)
You also have a set.
So:
brand_objs = []
for brand_data in brands:
brand = Brand.objects.get(**brand_data)
brand_objs.append(brand)
instance.brands.set(brand_objs)
But the usage could differ, I can imagine that you'd also want to be able to just add one, or more, brands? But could use different end points for that?
Endpoints example
api/incentive/1/brands # get
api/incentive/1/brands # post, set brands?
api/incentive/1/brands/add # add one or more?
api/incentive/1/brands/remove # remove specific one or more?
Add instance.brands.clear() like so:
This will clear related brands so you can update them freshly.
def update(self, instance, validated_data):
brands = validated_data.pop('brands', None)
instance = super().update(instance, validated_data)
# The condition below will update brands only if brands were
# specified in the request body
if brands is not None:
instance.brands.clear() # Clear related brands
for brand_data in brands:
brand = Brand.objects.get(**brand_data)
instance.brands.add(brand)
return instance

Best way to modify the fields in response in django rest?

I have a serializer that gives this data
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'name', 'cost', 'currency')
class UserSerializer(serializers.ModelSerializer):
posts = PostSerializer(many=True)
class Meta:
model = User
fields = ('id', 'name')
and it gives the response like this,
{
"id": 1,
"name": "joe",
"posts": [
{
"id": 20,
"name": "first post",
"cost": 20.00,
"currency": "USD"
},
{
"id": 21,
"name": "second post",
"cost": 30.00,
"currency": "USD"
}
]
}
However I want to change/add the fields of the response based on few conditions,
Eg. if cost is less than 25, make it zero and add a discount field for every post.
This is how I am doing.
class MyPostView(APIView):
def get(request):
query_set = User.objects.all()
user_and_posts = UserSerializer(query_set)
response_data = user_and_posts.data
# I am modifying the serializer data here :<
for post in response_data['posts']:
post['discount'] = 10 # some value
if post['cost'] < 25:
post['cost'] = 0
return Response(serializer.data, status=status.HTTP_200_OK)
To me modifying the primitive data like this is not looking right,
is there any alternate way in django rest to do this?
or could've done better with serializer?
In general, what's the best way to alter the response data we get from serializer and
format it in the way client wants? In languages like Java, we will have serializer for model and another serializer for output.. Can I do something similar?
If it is something that is model related and can be derived by manipulating model variables, I would advise to add properties into your model
class Post(models.Model):
name = models.CharField(max_length=64)
cost = models.DecimalField(max_digits=8, decimal_places=2)
currency = models.CharField(max_length=3)
#property
def alternative_cost(self):
if self.cost < 25:
return 0
else:
return self.cost
Then you have to add the newly created property to serializer.

Django REST framework: get field of related model in serializer

I'm new to Django Rest Framework. I'm trying to get my ListAPI to show various fields of my Quiz (and related) models. It's working fine, except for my attempt_number field. I'm getting the right queryset, but I'm not sure how to get only the relevant value for every query. Users can take every quiz as many times as they want, and I want to show the queryset for each attempt, since the score etc. will be different.
My model setup is as follows:
class Quiz(models.Model):
title = models.CharField(max_length=15)
slug = models.SlugField(blank=True)
questions_count = models.IntegerField(default=0)
class Question(models.Model):
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
label = models.CharField(max_length=1000)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
answer = models.CharField(max_length=100)
is_correct = models.BooleanField('Correct answer', default=False)
class QuizTaker(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
correct_answers = models.IntegerField(default=0)
completed = models.BooleanField(default=False)
attempt_number = models.PositiveIntegerField(default=0)
My serializer for the ListAPI looks as follows:
class MyQuizListSerializer(serializers.ModelSerializer):
attempt = serializers.SerializerMethodField()
# etc..
class Meta:
model = Quiz
fields = "__all__"
def get_attempt(self, obj):
try:
quiztaker = QuizTaker.objects.filter(user=self.context['request'].user,
quiz=obj)
for attempt in quiztaker:
attempt_number = attempt.attempt_number
return attempt_number
If I do it like this, I always get the last value for attempt_number (because the loop overwrites the value). So then I tried to append it to a list instead, like this:
a = []
for attempt in quiztaker:
attempt_number = attempt.attempt_number
a.append(attempt_number)
return a
But then I get the list of attempts for every query, instead of the attempt number for each query. I.e. I get the following three times (because in this case there are three attempts):
{
"id": 4,
"attempt": [
1,
2,
3
]
},
But instead what I want is (and the same for attempt 2 and 3 etc.):
{
"id": 4,
"attempt": 1
},
So I tried doing it like this:
return a[attempt_number-1]
Hoping it would give me index zero for attempt number 1, 1 for 2, etc. But then I still just get the last attempt number (3 in this case). How can I solve this?
I also tried just using an IntegerField instead of a SerializerMethodField as follows:
attempt = serializers.IntegerField(read_only=True, source='quiztaker.attempt_number')
But it returned nothing.
If I correctly understood you, you want the list of attempts added to each quiz object.
{
"id": 4,
"attempts": [{
"id": 1,
"attempt_number": 1,
},
{
"id": 2,
"attempt_number": 2,
}...]
}
In that case, you should have a separate serializer for the QuizTaker model and serialize the objects in the SerializerMethodField.
class QuizTakerSerializer(serializers.ModelSerializer):
class Meta:
model = QuizTaker
fields = ('id', 'attempt_number')
class MyQuizListSerializer(serializers.ModelSerializer):
attempts = serializers.SerializerMethodField()
# etc..
class Meta:
model = Quiz
fields = "__all__"
def get_attempts(self, obj):
quiztakers = QuizTaker.objects.filter(user=self.context['request'].user,quiz=obj)
return QuizTakerSerializer(quiztakers, many=True).data
Honestly, your question is not very clear and it would help to edit it and make it clearer, giving the JSON structure you want to achieve. I also suspect your intended use of queryset isn't the actual Django meaning for a container of ORM objects.

Django REST API Multiple Records Same Category

There is a problem with Django Rest API , i have a 2 records in my creators table.
first records have category "gaming" and the second record have category "Education" all is ok for now , but when i add Thirds record with Category "Education"(same category of first record ) then i got the error when i call all records of That Category(gaming).
i want the multiple records of same category
model.py
class Creators(models.Model):
title=models.CharField(max_length=200)
link = models.CharField(max_length=200)
subscriber = models.PositiveIntegerField()
country = models.CharField(max_length=100)
email = models.CharField(max_length=100)
description = models.TextField(max_length=2000)
category = models.CharField(max_length=100)
socialLinks = models.TextField(max_length=2000)
class Meta:
ordering = ('title',)
serializer.py
class CreatorsSerializer(serializers.ModelSerializer):
class Meta:
model = Creators
fields = "__all__"
views.py
class CreatorsList(APIView):
def get(self,request,category):
creator = Creators.objects.get(category=category)
serializer = CreatorsSerializer(creator)
return Response(serializer.data)
def post(self):
pass
urls.py
url(r'^creators/(?P<category>\w+)/$', views.CreatorsList.as_view()),
when i run example.com/creators/Gaming/ then it gives me perfect output(for now i just have 2 records)
{
"id": 1,
"title": "channel1",
"link": "https://www.youtube.com/channel/UCJbPGzawDH1njbqV-D5HqKw",
"subscriber": 2000,
"country": "PK",
"email": "ishaq#gmail.com",
"description": "ddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnknd",
"category": "Gaming",
"socialLinks": "ddkndkndkdknkdnkndddkndkndkdknkdnkndddkndkndkdknkdnknd"
}
but when i added third records of same category (Gaming) the error shows
MultipleObjectsReturned at /creators/Education/
get() returned more than one Creators -- it returned 2!
i also added many=True in views.py but nothing happens even the category of 1 records also not working in this case
class CreatorsList(APIView):
def get(self,request,category):
creator = Creators.objects.get(category=category,many=True)
serializer = CreatorsSerializer(creator)
return Response(serializer.data)
def post(self):
pass
You need to use filter instead of get in this line
creator = Creators.objects.get(category=category)
the method get() expect to return only one record and give this error if it returns more. Then the correct is :
creator = Creators.objects.filter(category=category)

Django Filtering based on field values in

I am using Django and I want to use filters in that
My product and company Models are
class Product(models.Model):
name = models.CharField(max_length=200)
companyId = models.ForeignKey(Comapany)
class Company(models.Model):
domain = models.CharField(max_length=200)
I want to retrieve Products based on currently user's companyId. So I have implemented My view like this..
class ListProducts(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAdminUser,)
def get(self, request):
if request.user.is_authenticated():
userCompanyId = request.user.get_profile().companyId
products = Product.objects.filter(companyId__id__exact = userCompanyId)
serializer = ProductSerializer(products)
return Response(serializer.data)
My Product Data
{
"_id": ObjectId("5284ceaae9cfff79368e1f29"),
"companyId": "528458c4bbe7823947b6d2a3",
"name": "Apple Juice"
}
{
"_id": ObjectId("5267bb4ebbe78220588b4567"),
"companyId": "52674f02bbe782d5528b4567",
"name": "Small Soft & Moist Cranberry"
}
My company Data
{
"_id": ObjectId("528458c4bbe7823947b6d2a3"),
"domain": "Manufacturing"
}
{
"_id": ObjectId("52674f02bbe782d5528b4567"),
"domain": "Manufacturing"
}
I am getting output as []
The problem is that while studying filters from django doc I am not able to get it.. SO please help me..
I resolved the error. Actually The way of specifying how Product model's companyId references Id field of company model was wrong. So I saw How userprofile references user model .I am mentioning here my changes.
Product Model
class Product(models.Model):
name = models.CharField(max_length=200)
company = models.ForeignKey(Company,null=True)
View
class ListProducts(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAdminUser,)
model = Product
def get(self, request):
if request.user.is_authenticated():
userCompanyId = request.user.get_profile().companyId
products = Product.objects.filter(company = userCompanyId)
serializer = ProductSerializer(products,many=True)
return Response(serializer.data)
put company_id in Company data like this
{
"_id": ObjectId("5284ceaae9cfff79368e1f29"),
"company_id": "528458c4bbe7823947b6d2a3",
"name": "Apple Juice"
}
Obviously your user profile model's companyId is not an integer field, so you should adjust your Product model to have a compatible companyId type. Oh and yes: don't you have a Company model ?
This line:
ProductSerializer(products)
Should be:
ProductSerializer(products, many=True)
Otherwise though your issue doesn't seem to be related to Django REST framework, just to the particular filter you're running not returning any results.
Double check that request.user.get_profile() is returning a correct profile, double check that userCompanyId is set correctly, and double check the number of results returned by Product.objects.filter(companyId__id__exact=userCompanyId).count().
I'd guess you either have userCompanyId set incorrectly, or there simply aren't any matching Product records.

Categories

Resources