2

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.

6
  • Please show how your serializer looks like. Commented Oct 27, 2022 at 14:00
  • Hej @JalilMarkel, I updated the question and provided all the serializers. I have changed the name here and there and minimized the number of fields to make things understandable. Commented Oct 27, 2022 at 14:19
  • # "file": base64.b64encode(f.read()).decode(), I'm getting a syntax waning here. I'm getting a typo warning here. My solution below seems to boot correctly when I fix this manually. stackoverflow.com/a/64156766/2138283 Commented Oct 29, 2022 at 16:02
  • What is test_file here? a file object? or a base64 file representation? Commented Oct 31, 2022 at 6:39
  • Hi @JPG. The "file": test_file is a file. Its not base64 as I have commented out base64 line. Commented Oct 31, 2022 at 8:22

1 Answer 1

1

The only way to solve it is to have separate views to handle images and other text fields. I cannot see your views but I assume you are handling the image and the text fields in the same views.

Sign up to request clarification or add additional context in comments.

1 Comment

you are right but here the problem is its a requirement. So a smart work around is required.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.