How to Send List of IDs via multipart/form-data in Django REST Framework
I am working on a Django REST Framework API where I need to send a list of IDs in a multipart/form-data request to create a many-to-many relationship in the database. While everything works perfectly when using JSON as the request format, I face issues when switching to multipart/form-data because the list of IDs doesn't serialize correctly.
Here's my serializer:
class AddOrUpdateContractSerializer(serializers.ModelSerializer):
deputy_agent = serializers.ListField(
child=serializers.IntegerField(), required=False, allow_empty=False
)
product_type = serializers.ListField(
child=serializers.IntegerField(), required=False, allow_empty=False
)
referral_type = serializers.ListField(
child=serializers.IntegerField(), required=False, allow_empty=False
)
customer_id = serializers.IntegerField(required=False)
class Meta:
model = Contract
exclude = ["id", "product", "is_deleted", "state", "customer"]
Here's my view
class AddContract(APIView):
@extend_schema(request=AddOrUpdateContractSerializer, tags=["contract"])
def post(self, request):
serialized_data = AddOrUpdateContractSerializer(data=request.data)
if serialized_data.is_valid(raise_exception=True):
service = ContractService(
serialized_data=serialized_data.validated_data, user=request.user
)
service.create_contract()
return Response(
status=status.HTTP_200_OK,
data={"detail": "contract created successfully"},
)
And the service for creating the contract:
def create_contract(self):
items = ["customer_id", "deputy_agent", "product_type", "referral_type"]
customer = CustomerSelector.get_customer_by_id(
self.serialized_data.get("customer_id", None)
)
deputy_agent = self.serialized_data.get("deputy_agent", None)
product_type = self.serialized_data.get("product_type", None)
referral_type = self.serialized_data.get("referral_type", None)
self.remove_unnecessary_items(items)
contract = Contract(customer=customer, **self.serialized_data)
contract.full_clean()
contract.save()
if deputy_agent:
deputy_agents = DeputyAgent.objects.filter(id__in=deputy_agent)
contract.deputy_agent.add(*deputy_agents)
if product_type:
product_types = ComboBoxsOptions.objects.filter(id__in=product_type)
contract.product_type.add(*product_types)
if referral_type:
referral_types = ComboBoxsOptions.objects.filter(id__in=referral_type)
contract.referral_type.add(*referral_types)
PROBLEM
When sending the data as multipart/form-data, lists like deputy_agent, product_type, and referral_type are received as strings instead of lists
Why I Need multipart/form-data I cannot use JSON because I also need to upload image files along with these IDs in the same request.
Example Request
Content-Type: multipart/form-data
customer_id: 1
deputy_agent: [1, 2, 3]
product_type: [4, 5, 6]
image: <uploaded_file>
Question How can I handle lists of IDs in multipart/form-data properly in Django REST Framework? Is there a best practice or workaround for this issue?
The issue you're encountering is related to how Django handles multipart/form-data and how it serializes data sent in that format. When you send lists like deputy_agent, product_type, and referral_type in a multipart/form-data request, they get serialized as strings, not as actual lists.
To solve this, you can make use of a custom serializer method that handles the parsing of those string representations of lists into actual Python lists.
Solution:
Override the
to_internal_valuemethod: This will allow you to manually parse the string into a list.Use
ListFieldin conjunction with a custom method: Since the data is being sent as a string inmultipart/form-data, you need to manually handle this conversion before it gets validated.
Here’s how you can modify your serializer to handle lists sent in multipart/form-data:
Updated Serializer:
from rest_framework import serializers
class AddOrUpdateContractSerializer(serializers.ModelSerializer):
deputy_agent = serializers.ListField(
child=serializers.IntegerField(), required=False, allow_empty=False
)
product_type = serializers.ListField(
child=serializers.IntegerField(), required=False, allow_empty=False
)
referral_type = serializers.ListField(
child=serializers.IntegerField(), required=False, allow_empty=False
)
customer_id = serializers.IntegerField(required=False)
class Meta:
model = Contract
exclude = ["id", "product", "is_deleted", "state", "customer"]
def to_internal_value(self, data):
# If any of the lists are passed as a string, try to parse them into a list
for field in ['deputy_agent', 'product_type', 'referral_type']:
if field in data and isinstance(data[field], str):
# Try to convert string to a list
try:
data[field] = [int(i) for i in data[field].strip('[]').split(',')]
except ValueError:
raise serializers.ValidationError(f"Invalid format for {field}, should be a list of integers.")
return super().to_internal_value(data)
Key Changes:
to_internal_valuemethod: This method checks if the field is received as a string (which can happen inmultipart/form-data), and attempts to parse it into a list of integers.split(','): Assumes that the list is being sent in a format like'1,2,3'. You can adjust the parsing logic if the format differs.
Example Request (Multipart Form Data):
Here’s what the request should look like when sent as multipart/form-data:
POST /api/contracts/ HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryE19zNvXG3RzrU5J3
------WebKitFormBoundaryE19zNvXG3RzrU5J3
Content-Disposition: form-data; name="customer_id"
1
------WebKitFormBoundaryE19zNvXG3RzrU5J3
Content-Disposition: form-data; name="deputy_agent"
1,2,3
------WebKitFormBoundaryE19zNvXG3RzrU5J3
Content-Disposition: form-data; name="product_type"
4,5,6
------WebKitFormBoundaryE19zNvXG3RzrU5J3
Content-Disposition: form-data; name="image"; filename="image.jpg"
Content-Type: image/jpeg
<image_binary_data>
------WebKitFormBoundaryE19zNvXG3RzrU5J3--
Final Notes:
- Field Parsing: The
to_internal_valuemethod ensures that thedeputy_agent,product_type, andreferral_typefields are parsed from strings into proper Python lists. - File Upload: The
imagefield will still work as expected inmultipart/form-data, as Django handles file uploads separately.
This should fix the issue you're facing when working with lists in multipart/form-data in Django REST Framework.
Let me know how this works out!