epalm
epalm

Reputation: 4433

How can I clear an image with Django Rest Framework?

I thought my problem was https://github.com/encode/django-rest-framework/issues/937 which should have been fixed by https://github.com/encode/django-rest-framework/pull/1003 but it appears, whether I send in None or empty string, DRF isn't happy.

I'm using Django 1.11.6 and DRF 3.7.7

class Part(models.Model):
    image = models.ImageField(null=True, blank=True)

class PartSerializer(serializers.ModelSerializer):
    class Meta:
        model = Part
        fields = ('id', 'image')

class PartDetail(generics.RetrieveUpdateAPIView):
    queryset = Part.objects.all()
    serializer_class = PartSerializer
    parser_classes = (MultiPartParser, FormParser)

# put image, works fine
with tempfile.NamedTemporaryFile(suffix='.jpg') as fp:
    image = Image.new('RGB', (100, 200))
    image.save(fp)
    fp.seek(0)
    data = {'image': fp}
    self.client.put('/path/to/endpoint', data, format='multipart')

# clear image, attempt #1
data = {'image': None}
self.client.put('/path/to/endpoint', data, format='multipart')
AssertionError: {'image': ['The submitted data was not a file. Check the encoding type on the form.']}

# clear image, attempt #2
data = {'image': ''}
self.client.put('/path/to/endpoint', data, format='multipart')
AssertionError: <ImageFieldFile: None> is not None

Upvotes: 10

Views: 5602

Answers (4)

garrett mitchener
garrett mitchener

Reputation: 111

I ran into something like this trying to write an Angular app that contacts a Django system through Django REST framework. DRF automatically generates forms for updating objects. If there's a FileField on an object and you don't upload a file into the update form when you submit it, the framework can automatically remove a previously uploaded file and leave the object with no file at all. The object's field is then null. I wanted my app to have this capability, that is, objects can have an attached file, but it isn't required, and a file can be attached and later removed. I tried to accomplish the removal by constructing a FormData object and sending it as a PUT request, but I couldn't figure out exactly what value to specify for the file field to make DRF delete the previously uploaded file, like what happens in DRF's automatically generated forms.

These didn't work:

    let fd = new FormData();
    fd.set('my_file', null); // TypeScript wouldn't let me do this
    fd.set('my_file', ''); // Same error as your attempt #2
    fd.set('my_file', new Blob([]); // Error about an empty file

What finally worked was

    fd.set('my_file', new File([], ''));

which apparently means an empty file with no name. With that, I could send a PUT request that deletes the file attached to the object and leaves the resulting FileField null:

    this.http.put<MyRecord>(url, fd);

where this.http is a an Angular HttpClient. I'm not sure how to construct such a PUT request in Python.

To leave the file in place, don't set anything for 'my_file' on the FormData.

On the Django side, I was using a subclass of ModelSerializer as the serializer, and the underlying FileField in the Model had options blank=True, null=True.

Upvotes: 5

Jason
Jason

Reputation: 11363

This is covered in the docs for FileField.delete

https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.fields.files.FieldFile.delete

I would create an update method on your serializer that would clear the image using the ORM call

def update(self, instance, validated_data):
     instance.part.delete(save = True)

or something similar.

Upvotes: 1

qiliux
qiliux

Reputation: 19

Please make sure that I reimplement your code.

Django version:2.0
DRF version:3.7.7 as you say in the question.

Firstly, the conclusion is that the method in your attempt #2 is right in my tests. However, attempt #1 has no effect and my error is the same as yours. However, the images saved before is not deleted in the filesystem. The followings are my TestCase. Make sure that you use the client provided by the DRF rather than the django itself.

from PIL import Image
from django.test import Client
import io
import os
import unittest
from rest_framework.test import APIClient
class PutTests(unittest.TestCase):
def generate_photo_file(self):
    file = io.BytesIO()
    image = Image.new('RGBA', size=(100, 100), color=(155, 0, 0))
    image.save(file, 'png')
    file.name = 'test222.png'
    file.seek(0)
    return file


def test_imagetest(self):
    """
    test put Image object
    :return:
    """
    self.client = Client()
    print(os.path.join(os.path.dirname( os.path.dirname(__file__) ), '2018s.jpg'))
    f = self.generate_photo_file()
    data={
        'image': f,
    }
    # if you want to insert image in the testCase please let class PartDetail inherits from ListCreateAPIView.
    self.client.post('/puttestimage/', data=data, format='multipart')

def test_image2test(self):
    # this won't work in my test.
    self.client = APIClient()
    data = {'image': None}
    self.client.put('/puttestimage/1/', data=data, format='multipart')
#
def test_image3test(self):
    # this will work in my test.
    self.client = APIClient()
    data = {'image': ''}
    self.client.put('/puttestimage/2/', data=data, format='multipart')

The basic idea is that I first insert one image into the database, they I clear this image by your attempt#1 and #2.

class PartSerializer(serializers.ModelSerializer):
# image = serializers.ImageField(max_length=None, allow_empty_file=True, allow_null=True, required=False)
class Meta:
    model = Part
    fields = ('id', 'image')

class PartDetail(generics.RetrieveUpdateAPIView):
    queryset = Part.objects.all()
    serializer_class = PartSerializer
    parser_classes = (MultiPartParser, FormParser)
urlpatterns = [
    url(r'puttestimage/(?P<pk>[0-9]+)/',     PartDetail.as_view(),name='imageput'),
    url(r'puttestimage/', PartDetail.as_view(), name='imageputs'),
]

Upvotes: 0

theguru42
theguru42

Reputation: 3378

You have to specify the image field explicitly to allow it to be null.

use this:

class PartSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(max_length=None, allow_empty_file=True, allow_null=True, required=False)

    class Meta:
        model = Part
        fields = ('id', 'image')

check docs for more details.

Upvotes: 8

Related Questions