Passing a JSON + image data to a post endpoint, ends in converting part of request data to string. The part which is converted to string contains the file as well. Here is the input data:
data = {
"external": "90000001",
"sales": [
{
"total": 4,
"quantities": {"xs": 2, "s": 4},
"product": {
"type": self.type.id,
"product_image": {
"name": "First test files",
"product_files": [
{
# "file": base64.b64encode(f.read()).decode(),
"file": test_file,
"description": "Patterns file.",
"type": "pattern_file",
}
]
},
},
}
],
}
I am sending request to my endpoint in the test in this way:
res: Response = self.client.post(self.create_ext_so_url, data)
It returns an error:
rest_framework.exceptions.ValidationError: {'sales': [ErrorDetail(string='Must specify at least one sales', code='blank')]}
Here is the sales data extracted in the run_validation(...)
>>> attrs.get("sales", [])
"{'total': 4, 'quantities': {'xs': 2, 's': 4}, 'product': {'type': 1, 'product_image': {'name': 'First test files', 'product_files': [{'file': <SimpleUploadedFile: test.svg (image/svg+xml)>, 'description': 'Patterns file.', 'type': 'pattern_file'}]}}}"
Here it is visible that the sales in converted to string and later it won't be available as an object/dictionary and the object won't be created, which makes it fail the test.
Here is the view if someone wants to check it.
@action(detail=False, methods=["POST"], url_path="create-sales")
def create_sales_order(self, request: Request)-> Response:
ser_class = self.get_serializer_class()
ser = ser_class(data=request.data)
ser.is_valid(raise_exception=True)
ser.save()
return Response(ser.data, status=HTTP_201_CREATED)
The serializers are defined like this:
class OrderSerializer(ModelSerializer[Order]):
order_lines = LineSerializer(many=True)
class Meta:
model = Order
fields = "__all__"
def validate(self, attrs: Any) -> Any:
order_lines = [
item for item in attrs.get("order_lines", []) if not is_empty_object(item)
]
if (
not order_lines
or not isinstance(order_lines, list)
or len(order_lines) == 0
):
raise ValidationError(
{"order_lines": ["Must specify at least one order line"]},
code="blank",
)
for line in order_lines:
if not line.get("product", None):
raise ValidationError(
{
"order_lines": [
"Cannot create Order Line without a Porduct on it."
]
},
code="blank",
)
elif not line.get("product").get("style", None):
raise ValidationError(
{"order_lines": ["Poroduct must contain style information."]},
code="blank",
)
return super(OrderSerializer, self).validate(attrs)
def create(self, validated_data: Dict[str, Any]) -> Order:
order_lines = validated_data.pop("order_lines")
order: Order = super(OrderSerializer, self).create(validated_data)
for item in order_lines:
item["order"] = order
if not item.get("product").get("id", None):
product = Product.objects.create(**item.get("product"))
item["product"] = product
OrderLine.objects.create(**item)
return order
class LineSerializer(ModelSerializer[Line]):
product = CreateProductSerializer()
rg_id = CharField(required=False)
class Meta:
model = Line
fields = "__all__"
extra_kwargs = {
"order": {"required": False},
}
def create(self, validated_data: Dict[str, Any]) -> Line:
p_ser = CreateProductSerializer(data=validated_data.pop("product"))
p_ser.is_valid(raise_exception=True)
validated_data["product"] = p_ser.save()
return super(LineSerializer, self).create(validated_data)
class CreateProductSerializer(ModelSerializer[Product]):
style = CreateStyleSerializer(required=False) # type: ignore
class Meta:
model = Product
fields = "__all__"
def run_validation(self, data: Any = ...) -> Any | None:
if not data:
return super(CreateProductSerializer, self).run_validation(data)
style = data.get("style", None)
if not style:
raise ValidationError(
{"style": ["Product must specify a style."]},
code="blank",
)
return super(CreateProductSerializer, self).run_validation(data)
def create(self, validated_data: Dict[str, Any]) -> Product:
style = validated_data.pop("style")
s_ser = CreateStyleSerializer(data=style)
s_ser.is_valid(raise_exception=True)
validated_data["style"] = s_ser.save()
return super(CreateProductSerializer, self).create(validated_data)
class CreateStyleSerializer(ModelSerializer[Style]):
style_files = StyleFileSerializer(many=True, required=False)
class Meta:
model = Style
fields = "__all__"
def run_validation(self, data: Any = ...) -> Any | None:
svg = data.get("svg", None)
style_files = data.get("style_files", None)
if style_files and len(style_files) == 0:
style_files = None
if not svg and not style_files:
raise ValidationError(
{
"svg": [
"Please provide either an svg or the style_files required to generate a `Style`"
]
},
code="blank",
)
return super(CreateStyleSerializer, self).run_validation(data)
def create(self, validated_data: Dict[str, Any]) -> Style:
style_files = validated_data.pop("style_files")
style = super(CreateStyleSerializer, self).create(validated_data)
style_files = [{**item, "style": style} for item in style_files]
sf_ser = StyleFileSerializer(data=style_files, many=True)
sf_ser.is_valid(raise_exception=True)
sf_ser.save()
return style
Apologies for a lot of code but cannot make it shorter. If it is not running then that is because I changed and posted minimized version with less (relevant) fields.
test_filehere? a file object? or a base64 file representation?"file": test_fileis a file. Its not base64 as I have commented out base64 line.