Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to POST a nested data and list of image

There are two models: Product and Picture. Each Product can have several Pictures. I have questions when I want to create a Product using POST. How to POST a nested object containing a ImageField list?

The Product model is:

class Product(models.Model):
    product_id = models.AutoField(primary_key=True)
    product_name = models.CharField(max_length=50)
    description = models.TextField(blank=True)

The Picture model is:

class Picture(models.Model):
    product = models.ForeignKey(Product, related_name='pictures')
    path = models.ImageField(null=False, upload_to='product_pic')
    description = models.CharField(max_length=255, null=True, blank=True)
    main = models.BooleanField()

I write the serializer.py as follow:

class PictureSerializer(serializers.ModelSerializer):
    class Meta:
        model = Picture
        fields = ('path', 'description', 'main')

class ProductSerializer(serializers.ModelSerializer): 
    pictures = PictureSerializer(many=True, required=False)

    class Meta:
        model = Product
        fields = ('product_id', 'product_name', 'pictures', 'description')

The view that I am using is:

class ProductEnum(generics.ListCreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    permission_classes = (IsAuthenticated, )

    def post(self, request, format=None):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

I am registering it in the urls as:

url(r'^api/products/$', views.ProductEnum.as_view()),

The questions are:

  • How could I test this POST api as django-rest-framework tells me that "Lists are not currently supported in HTML input"
  • How to use JSON to POST a Product resources with multiple Pictures. OR I must use multipart parser.
  • How to write cURL command?
like image 527
Scofield77 Avatar asked Jan 22 '15 16:01

Scofield77


2 Answers

You have to use multipart parser, as soon as you need to send binary data you basically only have to choices:

  1. weaken your REST approach and make an exception -> json cannot hold binary data
  2. encode every binary file with base64, keep json approach but you need to do de/encoding additionally for every request (not natively supported)

An approach that can be seen quite often is to create an (non rest) view to upload single/multiple files which create File or Document objects (returning an id on upload). Then you can use these id(s) which another request, in your case creating/updating your Product.

In general there is no easy way to do that because json doesn't support binary data.

like image 174
Johannes Reichard Avatar answered Sep 28 '22 09:09

Johannes Reichard


DRF makes this quite easy to do. You are close, you need to override ProductSerializer.create, something like this:

class ProductSerializer(serializers.ModelSerializer): 
    pictures = PictureSerializer(many=True, required=False)

    class Meta:
        model = Product
        fields = ('product_id', 'product_name', 'pictures', 'description')

    def create(self, validated_data):
        # Pop this from the validated_data first, since the serializer can't handle it.
        pictures = validated_data.pop('pictures')
        product = super().create(validated_data)
        # Now that we have a product to reference in the FKey, create the pictures.
        for picture in pictures:
            # `picture` has been run through the PictureSerialzer, so it's valid. 
            picture['product'] = product
            Picture.objects.create(**picture)
        return product

There's a full example in the docs, here: http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers

For your curl command, it's going to be something like:

curl -X POST http://localhost:8000/products/ -H 'ContentType: application/json' -d '{"pictures": [{"description": "first one"}, {"description": "second one"}], "product_name": "foobar"}'

like image 39
Symmetric Avatar answered Sep 28 '22 08:09

Symmetric