I have 2 models - Module and Room. A module can have zero or multiple rooms and a room can be added into multiple modules. So, there is a simple many-to-many relationship between them.
When I use post request, raw-data works, but not form-data.
module/models.py -
class Module(models.Model):
module_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
room_list = models.CharField(max_length = 100, blank=True)
rooms = models.ManyToManyField(Rooms, blank=True)
rooms/models.py -
class Rooms(models.Model):
room_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
level = models.CharField(max_length=100)
desc = models.TextField()
module/serializers.py -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(many=True)
class Meta:
model = Module
fields = '__all__'
def create(self, validated_data):
rooms_data = validated_data.pop('rooms')
module = Module.objects.create(**validated_data)
for data in rooms_data:
room = Rooms.objects.get(**data)
module.rooms.add(room)
return module
def update(self, instance, validated_data):
# Updating rooms
rooms_data = validated_data.get('rooms')
instance.rooms.clear()
for room_data in rooms_data:
room = Rooms.objects.get(**room_data)
instance.rooms.add(room)
# Updating other fields
fields = [
'title',
'desc',
'thumbnail',
'is_deleted',
]
for field in fields:
setattr(instance, field, validated_data[field])
instance.save()
return instance
rooms/serialier.py -
class RoomSerialize(serializers.ModelSerializer):
room_id = serializers.IntegerField()
class Meta:
model = Rooms
fields = "__all__"
module/views.py -
class add_module(APIView):
def post(self, request, format=None):
# Adding the rooms to module from room_list
new_request = request.data.copy()
room_list=[]
if 'room_list' in new_request:
room_list_data = list(new_request['room_list'].split(" "))
for room in room_list_data:
room_object = Rooms.objects.get(room_id=room)
room_serializer = RoomSerializer(room_object)
room_list.append(room_serializer.data)
new_request.update({'rooms':room_list})
# creating the module
module_serializer = ModuleSerializer(data=new_request)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'])
return Response(module_serializer.errors)
POST request body for updating a module in POSTMAN -
{
"module_id": 2,
"room_list": "1 2",
"title": "4",
"desc": "22",
}
Pls notice that while taking input of ManyToMany field - "rooms", I'm taking a string "room_list" as input that contains all the room_ids to be included. This works perfectly fine when I take input as raw-data in postman, but when I use form-data, it shows
{
"rooms": [
"This field is required."
]
}
What to do?
First of all, serializer fields with (many=True) create nested objects.
If you input data with Rooms as already serialized, then it means that you create other Rooms instances. It will be shown with if module_serializer.is_valid(): part. Therefore, if you intended to implement as just link already instantiated Rooms to Module, it's better to make it as primary key list.
Here are my example codes.
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(many=True, read_only=True)
def create(self, validated_data):
rooms_data = validated_data['room_list']
module = Module.objects.create(**validated_data)
for data in rooms_data.split(' '):
room = Rooms.objects.get(room_id=data)
module.rooms.add(room)
return module
def update(self, instance, validated_data):
# Updating rooms
rooms_data = validated_data.get('rooms')
instance.rooms.clear()
for room_data in rooms_data:
room = Rooms.objects.get(**room_data)
instance.rooms.add(room)
# Updating other fields
fields = [
'title',
'desc',
'thumbnail',
'is_deleted',
]
for field in fields:
setattr(instance, field, validated_data[field])
instance.save()
return instance
class Meta:
model = Module
fields = '__all__'
class AddModule(APIView):
def post(self, request, format=None):
# creating the module
module_serializer = ModuleSerializer(data=request.data)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'])
return Response(module_serializer.errors)
Any other parts are same as yours. Regardless of whether data are sent by form-data or json raw data, results are same as below.
Related
I have 2 models - Module and Room. A module can have zero or multiple rooms and a room can be added into multiple modules. So, there is a simple many-to-many relationship between them.
But when I define it in my module/models.py file, it is not taking any input as rooms. here are my files -
module/models.py -
class Module(models.Model):
module_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
rooms = models.ManyToManyField(Rooms)
rooms/models.py -
class Rooms(models.Model):
room_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
level = models.CharField(max_length=100)
is_deleted = models.BooleanField(default=False)
module/serializers.py -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(read_only=True, many=True)
class Meta:
model = Module
fields = "__all__"
module/views.py -
class add_module(APIView):
def post(self, request, format=None):
module_serializer = ModuleSerializer(data=request.data)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'], status = status.HTTP_201_CREATED)
return Response(module_serializer.errors, status = status.HTTP_400_BAD_REQUEST)
POST request body for creating a module in POSTMAN -
{
"rooms": [
{
"room_id": 2,
"title": "4",
"desc": "22",
"level": "2",
}
],
"title": "4",
"desc": "22",
}
With this request, module is being created, but no room is getting added in it.
Can someone tell me why my rooms are not getting added while creating modules?
You need to write an explicit serializer create method to support writable nested representations.
https://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
Example:
Given a RoomSerializer like this:
class RoomSerializer(serializers.ModelSerializer):
room_id = serializers.IntegerField()
class Meta:
model = models.Rooms
fields = '__all__'
We explicitly set room_id in RoomSerializer, to avoid <room_id> stripped away from the input data, and a MultipleObjectsReturned raised.
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(many=True) # remove *read_only=True*
class Meta:
model = Module
fields = "__all__"
def create(self, validated_data):
rooms_data = validated_data.pop('rooms')
module = Module.objects.create(**validated_data)
for data in rooms_data:
room, created = Rooms.objects.get_or_create(**data)
module.rooms.add(room)
return module
I have 2 models - Rooms and Modules. A module can contain many rooms and a room can be contained by many different modules. below are the models -
Rooms model -
class Rooms(models.Model):
room_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
level = models.CharField(max_length=100)
Module model -
class Module(models.Model):
module_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
is_deleted = models.BooleanField(default=False)
rooms = models.ManyToManyField(Rooms)
Module serializer -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(read_only=True, many=True)
class Meta:
model = Module
fields = "__all__"
Module view.py file -
class add_module(APIView):
def post(self, request, format=None):
module_serializer = ModuleSerializer(data=request.data)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'], status = status.HTTP_201_CREATED)
return Response("response":module_serializer.errors, status = status.HTTP_400_BAD_REQUEST)
How do I take multiple rooms as input in views.py file while creating my module object. Also if i want to test my API in postman, then how can i take multiple inputs in postman.
You can use a similar construction:
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(read_only=True, many=True)
class Meta:
model = Module
fields = "__all__"
def create(self, validated_data):
rooms = validated_data.pop('rooms')
...
for room in rooms:
...
return Response(...)
I have 2 models - Module and Room. A module can have zero or multiple rooms and a room can be added into multiple modules. So, there is a simple many-to-many relationship between them.
In post request, raw-data input works, but not form-data.
module/models.py -
class Module(models.Model):
module_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
rooms = models.ManyToManyField(Rooms, blank=True)
room_list = models.CharField(max_length = 100, blank=True)
rooms/models.py -
class Rooms(models.Model):
room_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
level = models.CharField(max_length=100)
module/serializers.py -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializerWrite(many=True)
class Meta:
model = Module
fields = '__all__'
def create(self, validated_data):
rooms_data = validated_data.pop('rooms')
module = Module.objects.create(**validated_data)
for data in rooms_data:
room = Rooms.objects.get(**data)
module.rooms.add(room)
return module
def update(self, instance, validated_data):
# Updating rooms
rooms_data = validated_data.get('rooms')
instance.rooms.clear()
for room_data in rooms_data:
room = Rooms.objects.get(**room_data)
instance.rooms.add(room)
# Updating other fields
fields = [
'title',
'desc',
'thumbnail',
'is_deleted',
]
for field in fields:
setattr(instance, field, validated_data[field])
instance.save()
return instance
rooms/serialier.py -
class RoomSerialize(serializers.ModelSerializer):
room_id = serializers.IntegerField()
class Meta:
model = Rooms
fields = "__all__"
module/views.py -
class add_module(APIView):
def post(self, request, format=None):
# Adding the rooms to module from room_list
new_request = request.data.copy()
room_list=[]
if 'room_list' in new_request:
room_list_data = list(new_request['room_list'].split(" "))
for room in room_list_data:
room_object = Rooms.objects.get(room_id=room)
room_serializer = RoomSerializer(room_object)
room_list.append(room_serializer.data)
new_request.update({'rooms':room_list})
# creating the module
module_serializer = ModuleSerializer(data=new_request)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'])
return Response(module_serializer.errors)
POST request body for updating a module in POSTMAN -
{
"module_id": 2,
"room_list": "1 2",
"title": "4",
"desc": "22",
}
Pls notice that while taking input of ManyToMany field - "rooms", I'm taking a string "room_list" as input that contains all the room_ids to be included.
This works perfectly fine when I take input as raw-data in postman, but when I use form-data, it shows -
{
"rooms": [
"This field is required."
]
}
What to do?
Nested serializers don't work with multipart/form-data.
Please refer to the following issues:
https://github.com/encode/django-rest-framework/issues/7650
https://github.com/encode/django-rest-framework/issues/7262
I have 2 models - Module and Room. A module can have zero or multiple rooms and a room can be added into multiple modules. So, there is a simple many-to-many relationship between them.
While updating the modules field using a put request, I don't want to update any rooms in it, I just want to add/remove rooms in the module. Here are my files -
module/models.py -
class Module(models.Model):
module_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
rooms = models.ManyToManyField(Rooms)
rooms/models.py -
class Rooms(models.Model):
room_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100)
desc = models.TextField()
level = models.CharField(max_length=100)
is_deleted = models.BooleanField(default=False)
module/serializers.py -
class ModuleSerializer(serializers.ModelSerializer):
rooms = RoomSerializer(read_only=True, many=True)
class Meta:
model = Module
fields = "__all__"
def create(self, validated_data):
rooms_data = validated_data.pop('rooms')
module = Module.objects.create(**validated_data)
for data in rooms_data:
room, created = Rooms.objects.get_or_create(**data)
module.rooms.add(room)
return module
rooms/serialier.py -
class RoomSerializerWrite(serializers.ModelSerializer):
room_id = serializers.IntegerField()
class Meta:
model = Rooms
fields = "__all__"
module/views.py -
class add_module(APIView):
def post(self, request, format=None):
module_serializer = ModuleSerializer(data=request.data)
if module_serializer.is_valid():
module_serializer.save()
return Response(module_serializer.data['module_id'], status = status.HTTP_201_CREATED)
return Response(module_serializer.errors, status = status.HTTP_400_BAD_REQUEST)
POST request body for updating a module in POSTMAN -
{
"module_id": 2,
"rooms": [
{
"room_id": 2,
"title": "4",
"desc": "22",
"level": "2",
}
],
"title": "4",
"desc": "22",
}
Can someone help me with the update function in module/serializer?
What I understood is you don't want to update your module's rooms with your post body, actually you want to replace them. So just clear every room from m2m relationship and add new rooms from request. Something like;
def update(self, instance, validated_data):
# get new rooms
rooms_data = validated_data.get('rooms')
# clear m2m relationship
instance.rooms_set.clear()
# I assume your rooms_data is a list that contains dicts with room fields
for room_data in rooms_data:
# get room instance
room, created = Rooms.objects.get_or_create(**room_data)
# Add it to m2m relationship
instance.rooms.add(room)
instance.save()
return instance
So if you do not send existing room to your serializer, it will be removed from m2m relationship on instance.
I'm making a ToDo app but having difficulties getting the api to allow a user to create a new list with multiple items via one api call. Each list belongs to a specific "room".
I get 400 Bad Request. If I leave the 'todo_items' off the POST data it works fine to create the ToDoList object.
Also, if I remove "user" from the Meta fields attribute for the CreateToDoItemSerializer, it'll create both the ToDoList object and the ToDoItem objects, but the "content" for each ToDoItem will be an empty string. Inside the create method of NewToDoListSerializer, the validated_data is returning a list of empty OrderedDict() objects for the key "todo_items". I'm not sure what to make of that.
my models:
class Room(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
slug = models.SlugField(max_length=255, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="rooms")
class ToDoList(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, blank=True)
room = models.ForeignKey(Room, related_name="todo_lists")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="todo_lists")
class ToDoItem(models.Model):
todo_list = models.ForeignKey(ToDoList, related_name="todo_items")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="replies")
content = FroalaField(options={'placeholder': '''Just start writing...
Highlight any text to bring up the editor.'''})
my serializers
class CreateTodoItemSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=User.objects.all())
class Meta:
model = ToDoItem
fields = ['pk', 'user', 'content']
def create(self, validated_data):
reply = ToDo.objects.create(**validated_data)
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, read_only=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)
my viewset (the relevant bits):
class ToDoListViewSet(viewsets.ModelViewSet):
queryset = ToDoList.objects.all()
serializer_class = ToDoListSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated]
renderer_classes = (renderers.TemplateHTMLRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer)
template_name = "react_base.html"
lookup_field = "slug"
def create(self, request, **kwargs):
self.serializer_class = NewToDoListSerializer
return super(ToDoListViewSet, self).create(request, **kwargs)
def perform_create(self, serializer):
instance = serializer.save(user=self.request.user)
the data I'm POSTing:
todoListTitle, todoItemContent, moreTodoItemContent are all strings. this.props.room.pk is an integer. this.props.csrfmiddlewaretoken is the csrfmiddlewaretoken
var newToDoListData = {
"room": this.props.room.pk,
"title": todoListTitle,
"todo_items": [{"content": todoItemContent}, {"content": moreTodoItemContent}],
"csrfmiddlewaretoken": this.props.csrfmiddlewaretoken
};
You need to make the todo_items in your serializer required = false. I am not sure I understand what your second issue is.
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, required=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)