djangoamazon-s3django-rest-framework

How do you add user-defined S3 metadata in when using django-storages


I am using django 3.1.0 with django-storages 1.12.3. I am using an S3 backend to store media files (Minio). I am trying to post the file to S3, and include additional user metadata to the S3 object. This additional metadata comes from the POST request used to upload the file to django. With S3, custom metadata can be added by including additional key-value pairs to the HTTP header for the POST request sent to the S3 server when the file is uploaded, where the custom keys would be prefixed with 'X-Amz-Meta-'.

I am using a FileField in a django model, and files are uploaded using a REST API endpoint. As far as I understand it, when django receives the file, it stores it temporarily, and then, when saving the FielField on the model instance, the file is posted to the S3 server. I want to modify the flow, so that the custom metadata is taken from the request and included in the post to the S3 server.

Any idea how I can take data from the initial request, and pass it to the header of the POST request being sent to S3?

Update

After trying Option 1 from Helge Schneider's answer, I did a little modification and got it to work.

I am using django rest framework, so I modified the serializer .save() method.

from .models import MyModel
from rest_framework import serializers

class MyModelSerialzer(serializers.ModelSerializer):

    class Meta:
        model = SessionFile
        fields = ['file', 'my_other_field']

    def save(self):
        file = self.validated_data['file']
        my_other_field = self.validated_data['my_other_field']
        mymodel = MyModel(file=file, my_other_field=my_other_field)
        options_dict = {"Metadata": {"metadata1": "ImageName", 
                                    "metadata2": "ImagePROPERTIES",
                                    "metadata3": "ImageCREATIONDATE"}
                        } 
        mymodel.file.storage.object_parameters.update(options_dict)
        mymodel.save()

Solution

  • There are two possible options:

    1. Update the parameters of the storage class before calling the save method

    One option would be to update the storage class from the filefield of the model before calling the save method and update the object_parameters attribute.

    views.py

    import MyModel
    
    
    def my_view(request):
        mymodel = MyModel()
        options_dict = {"Metadata": {"metadata1": "ImageName", 
                                     "metadata2": "ImagePROPERTIES",
                                     "metadata3": "ImageCREATIONDATE"}
                        } 
        mymodel.filefield.storage.object_parameters.update(options_dict)
        mymodel.filefield.save()
    
    

    2. Manually upload the file with boto3

    Another option would be to use a boto3 client to upload the file manually (Documentation with the upload_fileobj method.

    You can then set the object metadata with the following code taken from this answer:

    import boto3
    s3 = boto3.resource('s3')
    options_dict = {"Metadata": {"metadata1": "ImageName", 
                                 "metadata2": "ImagePROPERTIES",
                                 "metadata3": "ImageCREATIONDATE"}
    s3.upload_fileobj(file, bucketname, key, ExtraArgs=options_dict)
    

    Afterwards you have to set the name property of the FileField to the key in the S3 Bucket as described in this answer.

    object.filefield.name = 'file/key/in/bucket'