How to make an attribute read-only in serializers in DRF? - python

I have a serializer.
class MySerializer(serializers.ModelSerializer):
class Meta:
model = models.MyClass
My model class is:
class MyClass(models.Model):
employee = models.ForeignKey("Employee", on_delete=models.CASCADE)
work_done = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I want employee attribute to be read-only and should only show this value in it's field:
employee = Employee.objects.get(user=self.request.user)
How can I do this in serializers?

You can make it read-only by mentioning it on the serializer, and show the current user's value by modifying the init method in the following way :
class MySerializer(serializers.ModelSerializer):
class Meta:
model = models.MyClass
read_only_fields = [
'employee',
]
def get_employee(self, instance) -> Any:
employee = None
request = self.context.get('request')
if request and hasattr(request, 'user'):
employee = Employee.objects.get(request.user)
# if you want to show serialize value of employee
# then write a EmployeeSerializer and add following blocked code
# if employee:
# serializer = EmployeeSerializer(user, many=False)
# employee = serializer.data
return employee

extra_kwargs = {
'employee': {'read_only': True}
}
you can make it using extra_kwargs it overrides existing model behaviours

Related

How do I serialize specific field in Django ManyToMany field?

So, I'm trying to use Django Rest Framework for my project. I have two models Category and Content to be serialized, as below:
views.py
class Category(models.Model):
category_name = models.CharField(max_length = 20)
def __str__(self):
return self.category
class Content(models.Model):
category = models.ManyToManyField(Category)
body = models.TextField(blank=True, null=True)
created_at = models.DateField(auto_now_add=True, null=True)
def __str__(self):
return self.created_at
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['category_name']
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = [
'category', 'body', 'created_at',
]
Now the problem is, the JSON object returned has the id of each object in Category, NOT the category_name. So when I console.log in my front-end, I get (for example) [1, 4, 8] but what I need is (for example) ['Wine', 'Beer', 'Whiskey'].
How do I make that possible? Thanks in advance :)
You need to check out StringRelatedField I think.
https://www.django-rest-framework.org/api-guide/relations/#stringrelatedfield
You can serialize related fields as defined in __str__ method in your model.
And also you can serialize your every item in that field like:
{ id: 1, name: 'category name' }
You need to check nested serializers for done that:
https://www.django-rest-framework.org/api-guide/relations/#nested-relationships
For example:
You need something like nested serializer, you can check it from there:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ("id", "name", ) # You can specify whatever fields you want here.
class ContentSerializer(serializers.ModelSerializer):
category = CategorySerializer() # many=True if you want
class Meta:
model = Content
fields = ("id", "category", "body", )
I think what you want is StringRelatedField, return field that yo. The second one, nested relationships was extra
You can make a read only field in serializer and set the source as follows:
class ContentSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.category_name',
read_only=True)
class Meta:
model = Content
fields = [
'category', 'body', 'created_at',
]
You can then bind category_name from JSON response with your frontend.

Python Django saved posts computed method

Assuming im using the default django model, a Post model (code below) and a SavedPost model that links a User to a Post (if the certain user with the certain post exists then that post is saved for that user) and a Follower model that links 2 user (similar to SavedPost).
What im trying to do: An API that for a user, they get all posts for the users they follow, in addition each of these posts has an extra 'field' to say if that post is saved or not.
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post_type = models.CharField(max_length=1, choices=[('B', 'Blog'), ('V', 'Video')], default='B')
file_path = models.URLField(null=True)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class SavedPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can save a post only once.
unique_together = ('user', 'post')
class Follower(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user")
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name="follower")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can follow another user only once
unique_together = ('user', 'follower')
Post serilializer:
class PostSerializer(serializers.ModelSerializer):
"""
Nested serializer for post using SimpleUser and Kingdom.
"""
class Meta:
model = Post
fields = ('id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at')
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
API View:
#permission_classes([IsAuthenticated,])
#api_view(['GET'])
def get_following(request):
user = request.user
following = Follower.objects.filter(follower=user).values('user')
# saved_posts = SavedPost.objects.filter(user=user, post__user__in=following).order_by('-post__created_at')
posts = Post.objects.filter(user__in=following).order_by('-created_at')
serializer = PostSerializer(posts, many=True, context={'request': request})
return JsonResponse(serializer.data, safe=False)
So far with the view I made I can get all the posts that the request.user follows but it doesnt say if they are saved or not. I am looking for say 'is_saved' boolean on post to say if that post is saved for that user or not.
Any help/method to do this appreciated. Thank you.
Use serializers.SerializerMethodField as
class PostSerializer(serializers.ModelSerializer):
is_saved = serializers.SerializerMethodField()
def get_is_saved(self, post_instance):
return SavedPost.objects.filter(user=post_instance.user, post=post_instance).exists()
class Meta:
model = Post
fields = ['id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at', 'is_saved']
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
First of all, just to be clear, I will be defining the related_name option for the ForeignKeys in SavedPost - it's up to you to decide whether to implement this or not:
class SavedPost(models.Model):
user = models.ForeignKey(User, related_name="saved", on_delete=models.CASCADE)
post = models.ForeignKey(Post, related_name="saved", on_delete=models.CASCADE)
...
Now, in your PostSerializer, you could add this field (remember to add it to the fields variable in the Meta inner class - that is if you're using ModelSerializer):
class PostSerializer(serializers.ModelSerializer):
saved = SavedPostSerializer(many=True)
...
To finish it off, define your SavedPostSerializer - above PostSerializer, if in the same file/module:
class SavedPostSerializer(serializers.ModelSerializer):
class Meta:
model = SavedPost
fields = "__all__"
With this, your json should have a nested field with the saved key containing an array of SavedPosts, if there are any related to the Posts retrieved.

Unable to POST JSON data from multiple select element with Django REST Framework

I would like to be able to send an AJAX POST request to my API endpoint to create a new instance of my Asset model with multiple Category instances referenced in my Asset model, hence the many-to-many field type in my Asset model.
I'm able to successfully POST and create new Asset instances, however my category field won't accept any data at all. The category field remains empty when a new Asset instance is created. I think it has something to do with my CategorySerializer. I'm still learning how to use Django REST Framework so I'd appreciate if I could get some help figuring out how to work with serializers in Django REST Framework.
I've already tried modifying the AssetSerializer create method to handle parsing the JSON and validating the data but that hasn't worked. I've also tried other solutions suggested in other posts I've found on StackOverflow but haven't found anything that works for my situation.
Here's my serializers.py file:
class CategorySerializer(serializers.ModelSerializer):
name = serializers.CharField(required=False, read_only=True)
class Meta:
model = Category
fields = ('id', 'name')
class AssetSerializer(serializers.ModelSerializer):
name = serializers.CharField(allow_null=True)
description = serializers.CharField(allow_null=True)
manufacturer = serializers.CharField(allow_null=True)
uid = serializers.UUIDField(read_only=True, allow_null=True)
borrower = BorrowerSerializer(allow_null=True, read_only=True)
condition = serializers.ChoiceField(choices=Asset.CONDITION_TYPE, default='g', allow_null=True)
owner = serializers.ReadOnlyField(source='owner.username')
return_date = serializers.DateField(allow_null=True)
checked_out = serializers.BooleanField(allow_null=True)
category = CategorySerializer(required=False, many=True)
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
def update(self, instance, validated_data):
instance.borrower = validated_data.get('borrower', instance.borrower)
instance.return_date = validated_data.get('return_date', instance.return_date)
instance.checked_out = validated_data.get('checked_out', instance.checked_out)
instance.name = validated_data.get('name', instance.name)
instance.manufacturer = validated_data.get('manufacturer', instance.manufacturer)
instance.model = validated_data.get('model', instance.model)
instance.description = validated_data.get('description', instance.description)
instance.condition = validated_data.get('condition', instance.condition)
instance.category = validated_data.get('category', instance.category)
instance.save()
return instance
def create(self, validated_data):
return Asset.objects.create(**validated_data)
Here's my Asset model:
class Asset(models.Model):
"""Model representing an Asset"""
uid = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=200)
manufacturer = models.CharField(max_length=64)
model = models.CharField(max_length=128)
description = models.TextField()
category = models.ManyToManyField(Category)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
borrower = models.ForeignKey(Borrower, on_delete=models.CASCADE, null=True, blank=True)
checked_out = models.BooleanField(default=False)
return_date = models.DateField(null=True, blank=True)
CONDITION_TYPE = (
('e', 'Excellent'),
('g', 'Good'),
('f', 'Fair'),
('p', 'Poor'),
)
condition = models.CharField(
max_length=1,
choices=CONDITION_TYPE,
blank=True,
help_text='Asset condition')
class Meta:
ordering = ['return_date']
#property
def is_dueback(self):
if self.return_date and date.today() > self.return_date:
return True
return False
def display_category(self):
"""Create a string for the Category. This is required to display category in Admin."""
return ', '.join(category.name for category in self.category.all())
display_category.short_description = 'Category'
def __str__(self):
return f'{self.uid} - {self.name}'
def get_absolute_url(self):
return reverse('asset-detail', args=[str(self.uid)])
Here's my Category model:
class Category(models.Model):
"""Model representing an Asset category"""
name = models.CharField(max_length=128)
def __str__(self):
return self.name
I'd appreciate any help you could provide. Thank you in advance.
i'm almost new in DRF but i try to help. why you writing all the field in serializer when you using ModelsSerializer? not need to telling ModelSerializer what type of field should be because you are pointing to model in class Meta and DRF know about fields and type and etc . second about allow_null=True in serializer, when Model haven't null=True you can't except DRF can create a not null-able field for instance with null=True so if you wnt a field can be null just add null=True in Model class . for your problem about ManytoMantry field try to use Primary key relation for ManyToMany fields in your serializers then pass id of Category instances in list:
class AssetSerializer(serializers.ModelSerializer):
borrower = BorrowerSerializer(allow_null=True, read_only=True)
category = serializers.PrimaryKeyRelatedField(many=True, queryset=Category.objects.all())
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
read_only_fields = ( 'uid' , ) # this fields will be read_only
depending on how you using this serializer in your view for save and update have difference way. if your view is generics class so will do create and update itself by POST and PUT method .and for other class view that isn't belong to generics DRF view you can using serializer.save() to create a new instance.wish help you.
pass data something like:
{
"name" : "foo",
"manufacture" : "foo",
.
.
.
"category" : [1,2,3,24,65]
}

Type error to create and update my list in django rest framework

I'm trying to use my api to create and update products in a bundle. I did so:
model.py
class Business(models.Model):
name = models.CharField(max_length=155)
class Product(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
class Meta:
verbose_name = "Product"
class Bundle(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
products = models.ManyToManyField(Product, related_name="bundles",blank=True, null=True, through="BundleProduct")
class Meta:
verbose_name = "Bundle"
def __str__(self):
return self.name
class BundleProduct(models.Model):
bundle = models.ForeignKey(Bundle, on_delete=models.CASCADE, related_name="bundleproducts")
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="bundleproducts")
number = models.IntegerField(default=1)
class Meta:
verbose_name = "Bundle of Product"
def __str__(self):
return str(self.product.name) + " do " + self.bundle.name
def get_absolute_url(self):
return reverse("BundleProduct_detail", kwargs={"pk": self.pk})
And here is my serializers.py:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = "__all__"
class BundleProductSerializer(serializers.ModelSerializer):
class Meta:
model = BundleProduct
fields = "__all__"
class BundleSerializer(serializers.ModelSerializer):
class Meta:
model = Bundle
fields = "__all__"
My viewset.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
model = Product
class BundleProductViewSet(viewsets.ModelViewSet):
queryset = BundleProduct.objects.all()
serializer_class = BundleProductSerializer
model = BundleProduct
class BundleViewSet(viewsets.ModelViewSet):
queryset = Bundle.objects.all()
serializer_class = BundleSerializer
model = Bundle
When I try to post some products in bundleproducts I receive "Incorrect type. Expected pk value, received list."
Reading about this error, I found some issues relating to PrimaryKeyRelatedField and SlugRelatedField. I know I need to override but I have no idea how to do it.
It's an example of how to post would works:
{
"number": 1,
"bundle": 2,
"product":
[
1,
2
]
}
After watching the video commented by Neil, I created the following method:
class BundleSerializer(
serializers.ModelSerializer
):
products = ProductSerializer(many=True)
def create(self, validated_data):
products = validated_data.pop('products')
bundle = BundleProduct.objects.create(**validated_data)
for product in products:
BundleProduct.objects.create(**product, bundle=bundle)
return Bundle
class Meta:
model = Bundle
fields = "__all__"
But doesn't work. I receive this error: "TypeError at /api/v1/bundle/
'name' is an invalid keyword argument for this function"
If you are making post via BundleSerializer you need to pass products with list of ProductSerializer data not just id since products in BundleSerializer is accepting productsSerializer data. You are getting type error 'name' is an invalid keyword argument for this function" because your validated_data contain name and BundleProduct object Does not have name field.And you are creating BundleProduct objects with validated_data.
Create bundle object and pass id of bundle object to BundleProduct object.
If you do not want to create product and just pass existing product id you need to make ListField
You need to Override get_fields and check the requests
override to_representation to return always List of ProdutSerializer Data
Override create for POST request
Override update for PUT and PATCH Request
Below is solution for POST Request
For PATCH AND PUT Request you need to override update method of ModelSerializer and handle the products accordingly.
class BundleSerializer(serializers.ModelSerializer):
def create(self, validated_data):
products = validated_data.pop('products')
bundle = Bundle.objects.create(**validated_data)
for product_id in products:
product = get_object_or_404(Product, pk=product_id)
BundleProduct.objects.create(product=product, bundle=bundle)
return bundle
class Meta:
model = Bundle
fields = "__all__"
def to_representation(self, instance):
repr = super().to_representation(instance)
repr['products'] = ProductSerializer(instance.products.all(), many=True).data
return repr
def get_fields(self):
fields = super().get_fields()
if self.context['request'].method in ['POST', "PATCH","PUT"]:
fields['products'] = serializers.ListField(
write_only=True,
child=serializers.IntegerField()
)
return fields
sample POST data to BundleSerializer
{
"products":[1,2],
"name":"Offer One",
"description":"description",
"price":1212,
"business":1
}
In my experience, if you want to update a model and a related model in one request, with DRF, the easiest way to do this is to override the "create" method of a serializer. There's a good video on this here which I used as my reference: https://www.youtube.com/watch?v=EyMFf9O6E60
The issue here is that you are posting a list to BundleProduct's product field yet it is an ForeignKey. To join Bundle to a Product, simply POST:
{
"bundle": 2,
"product" 1,
"number": 1
}
You can repeat this:
{
"bundle": 2,
"product" 4,
"number": 1
}
to add yet another product 4 to the same bundle and so on. Just make sure you do them one by one and not in a list as you had done earlier.

How to get JSON data in django rest framework without tying it to a model

I'm sending data like below to my django rest framework app.
{
"email": "sa#gmail.com"
"profile": {
stripe_token: "tok_15iFaJRbriArhT",
stripe_id: "cus_5u3iksCCw",
quantity: "5"
},
"subscription": {
subscription_name: "noidea"
}
}
Based on my serializers and models I can get everything except quantity because I don't have a field called quantity on any of my models. But I would simply like to grab the value sent in the quantity field and then do something with it.
This is what I'm doing so far:
serializers.py
class UserProfilePaymentSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('stripe_id', 'subscribed','stripe_token')
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscriptions
fields = ('subscription_name',)
class PaymentSerializer (serializers.ModelSerializer):
profile = UserProfilePaymentSerializer()
subscription = UserSubscriptionSerializer()
class Meta:
model = User
fields = ('email', 'profile','subscription',)
def update (self, instance, validated_data):
profile_data = validated_data.pop('profile')
subscription_data = validated_data.pop('subscription')
print profile_data
print subscription_data
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
telephone_number = models.CharField(max_length=100)
stripe_id = models.CharField(max_length=255)
subscribed = models.BooleanField(default=False)
stripe_token = models.CharField(max_length=255)
class Subscriptions(models.Model):
user = models.ManyToManyField(User)
subscription_name = models.CharField(max_length=100)
Add quantity field to UserProfilePaymentSerializer. Then you can access quantity field in your validated data dictionary.
class UserProfilePaymentSerializer(serializers.ModelSerializer):
quantity = serializers.CharField()
class Meta:
model = UserProfile
fields = ('stripe_id', 'subscribed','stripe_token')
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscriptions
fields = ('subscription_name',)
class PaymentSerializer (serializers.ModelSerializer):
profile = UserProfilePaymentSerializer()
subscription = UserSubscriptionSerializer()
class Meta:
model = User
fields = ('email', 'profile','subscription',)
def update (self, instance, validated_data):
....
quantity = validated_data['profile']['quantity']
....

Categories

Resources