5

I'm using boto3 to upload files to S3 and save their path in the FileField.

class SomeFile(models.Model):
    file = models.FileField(upload_to='some_folder', max_length=400, blank=True, null=True)

For the above model the following code works to create a record.

ff = SomeFile(file='file path in S3')
ff.full_clean()
ff.save()

Now, when I use ModelSerializer to do the same.

class SomeFileSerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeFile
        fields = ('file')

I get this error after running the code below

rest_framework.exceptions.ValidationError: {'file': [ErrorDetail(string='The submitted data was not a file. Check the encoding type on the form.', code='invalid')]}

serializer = SomeFileSerializer(data={'file': 'file path to S3'})
serializer.is_valid(raise_exception=True)

I need help in setting up the serializer to accept file path without actually having the file.

Sai Chander
  • 829
  • 1
  • 6
  • 15
  • why not just use `urlfield`? save files with AWS handler fucntions and response will be signed url which you can save in `urlfield` – ans2human Jan 13 '20 at 05:20
  • I'm new to Django. I don't want to store the full URLs but only want to store relative paths. I also assumed that using FileField would help me in reducing lesser code and all the articles which used boto3 used FileField. Also, If we change to some kind of on premise deployment where the images could be stored locally, having a FileField could help in rewriting some stuff – Sai Chander Jan 13 '20 at 06:08
  • Does this answer your question? [Django REST Framework and FileField absolute url](https://stackoverflow.com/questions/23918619/django-rest-framework-and-filefield-absolute-url) – Oleg Russkin Jan 13 '20 at 06:43
  • @OlegRusskin, I've seen that one already. It's actually useful in reading the data using serializer but I'm unsure, how would the serializer know what to do when writing the records. – Sai Chander Jan 14 '20 at 05:02
  • You can use different serializers for read and write. For read use URLField or MethodField to respond with url, for write - FileField. Also, uploading a file may be completely separate operation / view. Or it can accept url and download file from provided url, validate, save to S3... – Oleg Russkin Jan 14 '20 at 05:44
  • @OlegRusskin , I get what you said for reads. Sounds good. As for writes, the file is already uploaded to cloud from somewhere else. Now, I want to insert a record in the table with reference to that uploaded file. I don't want to download from the url. Instead save the path directly. – Sai Chander Jan 15 '20 at 08:17

1 Answers1

2

I was really in the same situation, and it was hard to find the solution on the web.

We have two options to solve this problem.

1. Passing data directly to save method

  • Read action: use serializer's read only ImageField
  • Write action: pass kwargs to save method

serializers.py

from rest_framework import serializers

class SomeFileSerializer(serializers.ModelSerializer):
    file = serializers.ImageField(read_only=True)

    class Meta:
        model = SomeFile
        fields = ('file')

views.py

serializer = SomeFileSerializer(data={'file': 'file path to S3'})
serializer.is_valid(raise_exception=True)

# for put method
serializer.save(file=request.data.get('file'))

# for patch method (if partial=True in serializer)
if request.data.get('file'):
    serializer.save(file=request.data.get('file'))
else:
    serializer.save()

2. Using CharField instead of ImageField

  • Read action: override to_representation function to response absolute url
  • Write action: use CharField to avoid ImageField's validation and action

serializers.py

from rest_framework import serializers

class SomeFileSerializer(serializers.ModelSerializer):
    file = serializers.CharField(max_length=400)

    class Meta:
        model = SomeFile
        fields = ('file')

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        if instance.file:
            # update filename to response absolute url
            representation['file'] = instance.file_absolute_url
        return representation

models.py

class SomeFile(models.Model):
    file = models.FileField(upload_to='some_folder', max_length=400, blank=True, null=True)

    @property
    def file_absolute_url(self):
        return self.file.url if self.file else None

Although I chose the 2nd solution because of drf_spectacular for documentation, the 1st solution would be easy to implement.

Kazukiyo
  • 31
  • 4
  • Got an error while running method 2 for the line representation['file'] = instance.file_absolute_url ```python AttributeError: 'collections.OrderedDict' object has no attribute 'file_absolute_url' ``` It seems that instance is an OrderedDict and has no function pointer – ruthuparna k Nov 16 '21 at 15:13
  • @ruthuparnak According to the error message, I guess you are accessing the serialized data with dot notation (ex. data.results.hoge). Instead accessing with dot, you can access with dictionary key name (ex. data['results']['hoge']). – Kazukiyo Nov 17 '21 at 07:20