2

In Django REST Framework (DRF), how do I support de-Serializing base64 encoded binary data?

I have a model:

class MyModel(Model):
   data = models.FileField(...)

and I want to be able to send this data as base64 encoded rather than having to multi-part form data or a "File Upload". Looking at the Parsers, only FileUploadParser and MultiPartParser seem to parse out the files.

I would like to be able to send this data in something like JSON (ie send the binary data in the data rather than the files:

{
 'data':'...'
}
Kevin Brown-Silva
  • 40,873
  • 40
  • 203
  • 237
Alex Rothberg
  • 10,243
  • 13
  • 60
  • 120

3 Answers3

4

I solved it by creating a new Parser:

def get_B64_JSON_Parser(fields):
    class Impl(parsers.JSONParser):
        media_type = 'application/json+b64'

        def parse(self, *args, **kwargs):
            ret = super(Impl, self).parse(*args, **kwargs)
            for field in fields:
                ret[field] = SimpleUploadedFile(name=field, content=ret[field].decode('base64'))
            return ret
    return Impl

which I then use in the View:

class TestModelViewSet(viewsets.ModelViewSet):
    parser_classes = [get_B64_JSON_Parser(('data_file',)),]
Alex Rothberg
  • 10,243
  • 13
  • 60
  • 120
2

This is an old question, but for those looking for an up-to-date solution, there is a plugin for DRF (drf_base64) that handles this situation. It allows reading files encoded as base64 strings in the JSON request.

So given a model like:

class MyModel(Model):
   data = models.FileField(...)

and an expected json like:

{
  "data": "data:image/png;base64,iVBORw0KGgoA ....",
  ...
}

The (des) serialization can be handled just importing from drf_base modules instead of the drf itself.

from drf_base64.serializers import ModelSerializer
from .models import MyModel

class MyModel(ModelSerializer):

    class Meta:
        model = MyModel

Just remember that is posible to get a base64 encoded file in javascript with the FileReader API.

beruic
  • 5,517
  • 3
  • 35
  • 59
Francisco Puga
  • 23,869
  • 5
  • 48
  • 64
0

There's probably something clever you can do at the serialiser level but the first thing that comes to mind is to do it in the view.

Step 1: Write the file. Something like:

fh = open("/path/to/media/folder/fileToSave.ext", "wb")
fh.write(fileData.decode('base64'))
fh.close()

Step 2: Set the file on the model. Something like:

instance = self.get_object()
instance.file_field.name = 'folder/fileToSave.ext' # `file_field` was `data` in your example
instance.save()

Note the absolute path at Step 1 and the path relative to the media folder at Step 2.

This should at least get you going.

Ideally you'd specify this as a serialiser field and get validation and auto-assignment to the model instance for free. But that seems complicated at first glance.

Carlton Gibson
  • 7,278
  • 2
  • 36
  • 46
  • It seems like a third option to decoding the binary in the `Serializer` or the `View` would be to do so in a `Parser`. My question would then be how to write a parser that knows which fields to `base64` decode. – Alex Rothberg Nov 06 '14 at 18:07
  • Well in the first case just hard code it. If you need it again have a parameter. – Carlton Gibson Nov 06 '14 at 18:13
  • You mean write something like: `parser_classes = (get_B64_JSON_Parser(('field_a',)),)` – Alex Rothberg Nov 06 '14 at 18:16
  • Yeah. But without thinking about it I have no clear idea of how that parser would run. How are you going to get validation? How are you going to feed the output to the FileField? Etc. I'd do it in the view – Carlton Gibson Nov 06 '14 at 18:23
  • The Parser could return a [`DataAndFiles`](https://github.com/tomchristie/django-rest-framework/blob/381771731f48c75e7d5951e353049cceec386512/rest_framework/parsers.py#L134) where `files['field_a'] = SimpleUploadedFile(name='fileToSave.ext', content=fileData.decode('base64'))` – Alex Rothberg Nov 06 '14 at 20:08