django-rest-framework: serializer from DATA don't update model ID - django

I've a simple serializer like this:
class CategoryCreationSerializer(serializers.ModelSerializer):
class Meta:
model = Category
Category has an id field (Django primary key) and I don't get why the Serializer is not updating it.
Scenario: I've a BlogPost with a given category. I'm updating the BlogPost with a different Category (already created on the database). So the POST request will have all the BlogPost information with the new Category JSON object with the updated id:
{
"id": 1,
"title": "My first blog post",
"category": {
"id": 3,
"name": "Techology"
}
}
The problem is that when I'm doing this:
category_data = data.get('category')
if category_data is not None:
category_serializer = CategoryCreationSerializer(data=category_data)
if category_serializer.is_valid():
blog_post.category = category_serializer.object
inside category title will be updated but the id field will be NONE.
Can you explain me why?
Cheers, Emanuele.

By default, the id field that is automatically generated by Django REST Framework is read-only. Because of this, the id will always be none and it will try to create a new Category if there isn't already one attached.
You can override this by adding you own id field to the serializer that is not read-only.
class CategoryCreationSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
class Meta:
model = Category

This is the official answer from the issue I created on django rest-framework git repository: https://github.com/tomchristie/django-rest-framework/issues/2114
Short answer though is that by default ModelSerializer will generate a
read-only field for the id. (In the generic views you'll see that the
id is set explicitly by the view code, based on the id in the URL)
You'll want to explicitly declare the id field on the serializer so
that you can make it read-write.
So I managed to solve my problem leaving the Serializer like the one in my question ( without id = serializers.IntegerField() )
category_data = data.get('category', None)
if category_data is not None:
category_serializer = CategoryCreationSerializer(blog_post.category, data=category_data)
if category_serializer.is_valid():
category_serializer.object.id = category_data.get("id", None)
blog_post.category = category_serializer.object

Related

How to customise update method for ManyToMany fields in Django Rest Framework

I have a few models with ManyToMany relationships between them and I need to override the create and update method to make the POST and PUT request work in DRF.
Here's my code so far:
class CreateFolderSerializer(serializers.ModelSerializer):
class Meta:
model = Folder
fields = ("id", "title", "description", "users")
def create(self, validated_data):
users = validated_data.pop(
'users') if 'users' in validated_data else []
folder = Folder.objects.create(**validated_data)
folder.users.set(users)
return folder
This create method works perfectly.
I tried re-creating the same logic for the update method, but it doesn't work:
class FolderSerializer(serializers.ModelSerializer):
documents = DocumentSerializer(many=True, read_only=True)
class Meta:
model = Folder
fields = '__all__'
def update(self, instance, validated_data):
users = validated_data.pop('users') if 'users' in validated_data else []
instance.users.set(users)
instance.save()
return instance
When I send a PUT request, the object does not get modified at all, it gets deleted altogether.
Any clue?
Thanks a lot.
Don' t set documents as read_only:
documents = DocumentSerializer(many=True)
By calling instance.users.set(users), you replace the precedent list, for example if you pass an empty list all associated users are removed. So if you want to leave the current users associated you need to insert their primary keys in the request data (for the users key), otherwise use instance.users.add instead of instance.users.set.

Serializing a Profile with associated Products with Django Rest Framework

I'm building a sample iOS app where users would buy and sell products, and I'm trying to design a page where the upper section will have basic profile details and the lower section will have the products that they're currently selling, something like this:
Sample Frontend Image
So I'm trying to create a Serializer / API endpoint which would give me the Profile, with the products that they are currently selling.
My Product model has a ForeignKey relationship to User:
class Product(models.Model):
def __str__(self):
return self.name
name = models.CharField(max_length=200)
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name="product_seller")
category = models.ForeignKey("Category", on_delete=models.CASCADE)
(...)
And I have a Profile model which has a one-to-one relationship with the default Django user. As this view will be based on Profiles, I think it would make more sense to serialize the User or the Profile model and get the products where they are the "seller".
So the JSON response that I want is something like this:
{
"id": 1,
"username": “some username”,
"profile_image": "http://192.168.1.101:8000/images/profile_pictures/732339C5-E419-4A3D-9022-A314416F5F02.png",
"description": “Some description for this particular profile.”
“products”: [
{ “id”: 1,
“image” = http://192.168.1.101:8000/images/abc.jpg,
},
{ “id”: 2,
“image” = http://192.168.1.101:8000/images/abc.jpg,
},
{ “id”: 3,
“image” = http://192.168.1.101:8000/images/abc.jpg,
}
]
}
What would be the best way to approach this using django-rest-framework?
I've tried using Nested Serializers, but my User or Profile models don't explicitly have a relationship to Product, so they haven't worked so far:
class SellerProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id", "images"]
class SellerSerializer(serializers.ModelSerializer):
products = SellerProductsSerializer(read_only=True, many=True)
profile_image = serializers.CharField(source="profile.profile_image")
description = serializers.CharField(source="profile.description")
class Meta:
model = User
fields = ["id", "username", "profile_image", "description", "products"]
I've also tried using the SerializerMethodField, which I think would work in this case, but I haven't quite figured out how I would filter the products where the particular user is the seller. The endpoints look like this:
.../application/api/seller/1
If I could access the "1" - which is the user id, I could filter with something like:
class SellerSerializer(serializers.ModelSerializer):
products = serializers.SerializerMethodField()
profile_image = serializers.CharField(source="profile.profile_image")
description = serializers.CharField(source="profile.description")
class Meta:
model = User
fields = ["id", "username", "profile_image", "description", "products"]
def get_products(self):
# get the id from the request / or the url
# filter the Product model.
I'm used to doing filtering on the viewsets, but not on the serializer itself. I think filtering on the viewset is not possible in this case, as I'm working with two different models and the one that I'm trying to filter is not the main model for the serializer.
I feel like there has to be a simple way to do this but I have been stuck for quite some time. Any help would be greatly appreciated.
You can retrieve a user with pk in the url,and return a response with that particular user, and the result of the following query:
Product.objects.filter(seller_id=pk)
And add the results to a list,and return a response that looks like this:
Response({
"Seller":Fetched User,
"Their Products":list of products
})

Django rest framework: how to fetch a foreign key based on a specific field (= not id)

Here, in the following code, TaskAppointment is a model and have a field state which is a foreign key to TaskAppointmentState. Using Django Rest Framework, I would like to update any TaskAppointment and to send a state for update. I have this relation that works perfectly. The only problem is that DRF filters TaskAppointmentState based on its pk (or id). TaskAppointmentState has two fields: pk and state (be careful it's not the same as the one in TaskAppointment). I would like to change the value state which is sent for update to a TaskAppointmentState based on TaskAppointmentState "state" field = not id, but state.
To be more precise, instead of doing instance.status_id = state.id in the following code, I would like to do this pseudo code instead: instance.status_id = "SELECT id FROM TaskAppointmentState WHERE state=request.GET['state'].
Maybe I could get rid of TaskAppointmentStateSerializer? If you have any working solution...
class TaskAppointmentStateSerializer(BaseSerializer):
class Meta:
model = TaskAppointmentState
class TaskAppointmentSerializer(BaseSerializer):
state = TaskAppointmentStateSerializer(required=False)
def update(self, instance, validated_data):
try:
state = validated_data.pop('state')
instance.status_id = state.id
except KeyError:
pass
# ... plus any other fields you may want to update
return instance
class Meta:
model = TaskAppointment
fields = ['id', 'date_start', 'date_end', 'state']
read_only_fields = ['id', ]
Yes, you don't need the TaskAppointmentStateSerializer.
You can use SlugRelatedField to set a relationship based on the value of a specific field in the related model:
class TaskAppointmentSerializer(BaseSerializer):
state = serializers.SlugRelatedField(slug_field='state',
queryset=TaskAppointmentState.objects.all())

How can I create model instance via Serializer without creating models from nested serialziers?

I have model with many links into it:
class Travel(BaseAbstractModel):
tags = models.ManyToManyField(
Tag,
related_name='travels',
)
owner = models.ForeignKey(
'users.TravelUser',
related_name='travel_owner'
)
payment = models.ForeignKey(
Payment,
related_name='travels',
)
country = models.ForeignKey(
Country,
related_name='travels,
)
........
Many of these models have only two fields with unique name and image.
I create serializer for each of these models and put them in TravelSerializer
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
Based on docs I override create() and update.
The problem is, when I sent JSON data, Django create each model from nested serializers. But I want to create only Travel instance. Also I want receive and respond serialized object not only pk field.
UPDATE
I solved this problem, put code in the answer. Now I can receive and respond with Serializer data without creating object.
But I think the DRF provides more elegant approach then I do. It is my first project with DRF, maybe I miss something and there's an easier solution.
I decide override to_internal_value() put it in custom serailizer and inherit all nested serializers from it:
class NestedRelatedSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
try:
pk = data['pk']
except (TypeError, KeyError):
# parse pk from request JSON
raise serializers.ValidationError({'_error': 'object must provide pk!'})
return pk
Get all pk from it and save in create and updated methods:
def update(self, instance, validated_data):
# If don't get instance from db, m2m field won't update immediately
# I don't understand why
instance = Travel.objects.get(pk=instance.pk)
instance.payment_id = validated_data.get('payment', instance.payment_id)
instance.country_id = validated_data.get('country', instance.country_id)
# update m2m links
instance.tags.clear()
instance.tags.add(*validated_data.get('tags'))
instance.save()
return instance
I'm not exactly sure I understand what you want to do, but could setting read_only_fields is the Meta class be what you need ?
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
class Meta:
read_only_fields = ('tags',)
See this section in the docs.

django rest framework nested objects get or create behaviour

I am creating an api end point using django-rest-framework for a specific json input. I have two related models like so (let's assume post can only have one category):
class Category(models.Model):
name = models.CharField(max_length=10)
slug = models.SlugField()
class Post(models.Model):
category = models.ForeignKey()
title = models.CharField(max_length=100)
text = models.CharField(max_length=256)
and my serializer are simple model serializers:
class CategorySerializer(ModelSerializer):
id = serializers.IntegerField(required=True)
class Meta:
model = Category
class PostSerializer(ModelSerializer):
id = serializers.IntegerField(required=True)
category = CategorySerializer()
class Meta:
model = Post
and my api view is very simple also:
class PostAPIView(mixins.CreateModelMixin, GenericAPIView):
serializer_class = PostSerializer
permission_classes = (IsAuthenticated,)
now in order to create posts I need to parse a json input like this:
{
"id": 10,
"pk": 10
"title": "Some title",
"text": "Some text",
"category": {
"id": 15,
"pk": 15
"name": "Best category",
"slug": "best-category"
}
}
in here 'pk' parameters are crucial for me, I want data to be created on my db using exact pk provided in json. Now if I make a post request and there are no posts with id:10 and categories with id:15 all is fine and data is written to db new records get inserted, but if there are any when rest-framework returns an error like ['Post id 10 already exists'], I would like matching records to be updated according to input instead. How can I do that?
You just need to add the UpdateMixin, just import it like the CreateModelMixin.
This mixin will implement the update and partial update methods, that will do what you want.
But you can not send a POST, for it you will need a PUT, or PATCH. You you want to do this on POST, I recommend you to implement your own create view method.