1

I need to store PNG files as blob/binary in the database and then be able to retrieve and show them.

Here's my model that has a binary field to store the image:

class ImageFile(models.Model):
    file = models.BinaryField(editable=True)

I created a widget based on this answer:

class BinaryFileInputWidget(forms.ClearableFileInput):
    def is_initial(self, value):
        return bool(value)

    def format_value(self, value):
        if self.is_initial(value):
            return f"{len(value)} bytes"

    def value_from_datadict(self, data, files, name):
        upload = super().value_from_datadict(data, files, name)
        if upload:
            return upload.read()

And I used it in admin.py like this:

@admin.register(ImageFile)
class ImageFileAdmin(admin.ModelAdmin):
    list_display = ["id"]
    formfield_overrides = {
        models.BinaryField: {"widget": BinaryFileInputWidget()},
    }

Then I encode the file as base64 in the view:

def image_view(request: HttpRequest, id: int):
    document_file = ImageFile.objects.filter(id=id).first()
    data = ""
    if document_file:
        data = base64.b64encode(document_file.file).decode(encoding="ascii")
    return render(request, "image.html", {"data": data})
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <pre>data:image/png;base64,{{ data }}</pre>
    <img src="data:image/png;base64,{{ data }}" alt="" />
  </body>
</html>

There is no error and the data is shown in the pre element but the img element fails to load the image. I tried using some online converters to check if the data is valid but they show errors saying the base64 string is invalid.

It loads properly if I convert the file to base64 in the widget before storing it in the database (return base64.b64encode(upload.read()).decode('ascii') instead of return upload.read()), and then convert it again in the view. But I cant use this method because I might need to compress the files and apparently base64 encoded data can't be compressed properly.

Sample output using my profile picture:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body cz-shortcut-listen="true">
    <pre>
data:image/png;base64,bx89PNGrnx1anx00x00x00rIHDRx00x00x01x00x00x00x01x00x08x03x00x00x00kxacXTx00x00x03x00PLTEx19x19x1dxffxffxffxd4xd5xe1xd3xd4xe1xd2xd3xe0x1fx11x11x15x10x10x13x18x18x1cx13x14x18x15x16x1ax16x17x1bxd5xd6xe2x15x15x19xfexfexffxdcxddxe9x13x12x16xdexdfxebxddxdexebxfdxfcxfdxd7xd8xe4xfdxfdxfex0fx0fx12xdaxdbxe7x0ex0ex12xd2xd3xdfxf9xf9xfbx1d9xf3xf3xf7x1ax1ax1exdfxe0xe9xfaxfaxfcxf1xf0xf3xdcxdcxe8xd7xd7xe3xf6xf6xf9xe0xe1xebxfcxfbxfdx174x1a7xebxecxf1xe4xe5xedxf7xf7xf9xe6xe7xeexd9xdaxe6xeexefxf4xa4xa2xadxc7xc8xd3xe8xe9xefx9bx98xa7x1ex1dxd0xd1xddxf5xf5xf8xc1xc2xcdxb8xb6xbfxb3xb1xbcxe2xe3xecxf8xf8xfbx9fx9dxa877xeexedxefllrdcixf4xf4xf8xd8xd9xe6x92x92x9cvvzxf1xf1xf5xb9xb9xc4xecxedxf3xa5xa6xaeTTX//3x8ax85x97CCFxe1xe0xe3xd8xd9xe4xdexddxe2xc3xc1xc7xc5xc4xce+a559x8bx8bx92x91x8cx9dxeaxeaxefxccxccxd8xf5xf4xf5xbcxbcxc8x86x83x92x97x94xa3xc7xc5xca9/Lxe4xe3xe5xabxa9xb2xb1xadxb6x85x80x91rrvxbfxbfxca225xcdxcbxd0x9cx95xa2xc0xbbxc3xeaxe9xebxa8xa5xb1xcexcdxcfx9dx9ax9bNOSxdexdexe8x88x82x7fxd5xd3xd9x8dx88x99xe7xe6xe9xb7xb6xb6xa1xa0xa2GZx92x8fx8ex98x95x96hhkdx8fx8fx97x88x88x8bxcaxc9xccxb4xb4xbcxadx7fx83xd1x98x93xc2x90x93IY0ATvx82x90vpx83x7fyx8cxafxadxbdxaaxa8xa9x84x7fx80x83xf0zBEx82JKPxd4xd1xd3zyxabxacxb7xe3xe3xebxaexadxaexeexacxa4xe8xa6x9fZRgxbdx8ax8axa0x93GFJxb8x86x87xfcxebxe8vx88xd9x9fx97ODe7WKl4Fxd8xd6xdbxfdx80Dxf9xbaxbaxbcxa5xa2xa2xf9xe4xe2xe9xeaxf3xf6xbbxb3xffxf6xf4x95iqxefxc3xbexe1xa8x9fA6VH/x1ex141xcfxcfxd8A8Qx93xx8dxe5xb4xc9xffxd9xe9x84lx83xdbxdaxddxd8xd8xd8xaezxa2vxcax93x91xaawxb3x82x84WWx97mtxaaxa7xb6YmernAxdbrjbvx90x81x90xffxc3xa7xa9x86x9cxfbxd6xd1xf6xd3xcfxf4xcaxc5x18x19x1dxfcxf1xf0xd6xa6xa3x9fsyxa0x80ngz4NROPxa3Y8xf5x83Oxc1h+x89N3xaex80jxfbxa9x83x91x89x97xf8x97ixd4xb3xc6xbax9bxafxf1xc3xd7xfaxcdxdfx8dsx89xe9xb6xb1xddxc4xc5x87hxffxeexecxcexbfxc3xe8xdfxe1xf7xc9xc2xb8xa4xabkx81vG1xe3x93m7Hxe0x86Zwvxfexdaxc9xe4xc9xbexfdx9fsxffxe9xdbxffxe1xd1xcaxa6xbaxd5xd6xd9xcbx9dxb3xecxb9xcexa7xa3xa0xdfxdbxd4xe3x00x00rxIDATxx9cxedx9dux1bxd9x16xc7xb93Cx1ex02x01x12x10x08txeeZx9cxb6Hxb4PJxa9Axa1xb4xddnuwxddxbaxf7uxebxdfxb7xebxbaxcbwxeexfbxe4x13DfRxb6xa4x8fx19xf6xffxe1xb3wfxfbxb9xbfx93sxcc=</pre
    >
    <img
      src="data:image/png;base64,bx89PNGrnx1anx00x00x00rIHDRx00x00x01x00x00x00x01x00x08x03x00x00x00kxacXTx00x00x03x00PLTEx19x19x1dxffxffxffxd4xd5xe1xd3xd4xe1xd2xd3xe0x1fx11x11x15x10x10x13x18x18x1cx13x14x18x15x16x1ax16x17x1bxd5xd6xe2x15x15x19xfexfexffxdcxddxe9x13x12x16xdexdfxebxddxdexebxfdxfcxfdxd7xd8xe4xfdxfdxfex0fx0fx12xdaxdbxe7x0ex0ex12xd2xd3xdfxf9xf9xfbx1d9xf3xf3xf7x1ax1ax1exdfxe0xe9xfaxfaxfcxf1xf0xf3xdcxdcxe8xd7xd7xe3xf6xf6xf9xe0xe1xebxfcxfbxfdx174x1a7xebxecxf1xe4xe5xedxf7xf7xf9xe6xe7xeexd9xdaxe6xeexefxf4xa4xa2xadxc7xc8xd3xe8xe9xefx9bx98xa7x1ex1dxd0xd1xddxf5xf5xf8xc1xc2xcdxb8xb6xbfxb3xb1xbcxe2xe3xecxf8xf8xfbx9fx9dxa877xeexedxefllrdcixf4xf4xf8xd8xd9xe6x92x92x9cvvzxf1xf1xf5xb9xb9xc4xecxedxf3xa5xa6xaeTTX//3x8ax85x97CCFxe1xe0xe3xd8xd9xe4xdexddxe2xc3xc1xc7xc5xc4xce+a559x8bx8bx92x91x8cx9dxeaxeaxefxccxccxd8xf5xf4xf5xbcxbcxc8x86x83x92x97x94xa3xc7xc5xca9/Lxe4xe3xe5xabxa9xb2xb1xadxb6x85x80x91rrvxbfxbfxca225xcdxcbxd0x9cx95xa2xc0xbbxc3xeaxe9xebxa8xa5xb1xcexcdxcfx9dx9ax9bNOSxdexdexe8x88x82x7fxd5xd3xd9x8dx88x99xe7xe6xe9xb7xb6xb6xa1xa0xa2GZx92x8fx8ex98x95x96hhkdx8fx8fx97x88x88x8bxcaxc9xccxb4xb4xbcxadx7fx83xd1x98x93xc2x90x93IY0ATvx82x90vpx83x7fyx8cxafxadxbdxaaxa8xa9x84x7fx80x83xf0zBEx82JKPxd4xd1xd3zyxabxacxb7xe3xe3xebxaexadxaexeexacxa4xe8xa6x9fZRgxbdx8ax8axa0x93GFJxb8x86x87xfcxebxe8vx88xd9x9fx97ODe7WKl4Fxd8xd6xdbxfdx80Dxf9xbaxbaxbcxa5xa2xa2xf9xe4xe2xe9xeaxf3xf6xbbxb3xffxf6xf4x95iqxefxc3xbexe1xa8x9fA6VH/x1ex141xcfxcfxd8A8Qx93xx8dxe5xb4xc9xffxd9xe9x84lx83xdbxdaxddxd8xd8xd8xaezxa2vxcax93x91xaawxb3x82x84WWx97mtxaaxa7xb6YmernAxdbrjbvx90x81x90xffxc3xa7xa9x86x9cxfbxd6xd1xf6xd3xcfxf4xcaxc5x18x19x1dxfcxf1xf0xd6xa6xa3x9fsyxa0x80ngz4NROPxa3Y8xf5x83Oxc1h+x89N3xaex80jxfbxa9x83x91x89x97xf8x97ixd4xb3xc6xbax9bxafxf1xc3xd7xfaxcdxdfx8dsx89xe9xb6xb1xddxc4xc5x87hxffxeexecxcexbfxc3xe8xdfxe1xf7xc9xc2xb8xa4xabkx81vG1xe3x93m7Hxe0x86Zwvxfexdaxc9xe4xc9xbexfdx9fsxffxe9xdbxffxe1xd1xcaxa6xbaxd5xd6xd9xcbx9dxb3xecxb9xcexa7xa3xa0xdfxdbxd4xe3x00x00rxIDATxx9cxedx9dux1bxd9x16xc7xb93Cx1ex02x01x12x10x08txeeZx9cxb6Hxb4PJxa9Axa1xb4xddnuwxddxbaxf7uxebxdfxb7xebxbaxcbwxeexfbxe4x13DfRxb6xa4x8fx19xf6xffxe1xb3wfxfbxb9xbfx93sxcc="
      alt=""
    />
  </body>
</html>

6
  • 2
    Storing file-like content in the database is typically not a good idea. Databases are not really designed to store large binary blobs as values. Commented Nov 18, 2024 at 11:53
  • @willeM_VanOnsem I know, usually I don't do things like this but in this project I need to store them in the database. Commented Nov 18, 2024 at 13:15
  • Is it possible to share a (sample) HTML output? Commented Nov 18, 2024 at 13:25
  • @willeM_VanOnsem Yes, I added a sample code snippet to the question. Commented Nov 18, 2024 at 13:55
  • 1
    The data looks quite strange: it repeats x00 (and other x a lot), and thus looks as if it was initially a string with \x00\x01, etc. where the backslash was dropped after wrapping it through a str. If this conversion is done, it starts making a bit sense as a binary string, but still not a base64 encoding. Commented Nov 18, 2024 at 15:30

1 Answer 1

1

I ended up creating a custom form (instead of widget) that reads the file from a FileField and then saves it in the model.

forms.py:

class ImageFileForm(forms.ModelForm):
    file_upload = forms.FileField()

    def save(self, commit=True, *args, **kwargs):
        instance = super(ImageFileForm, self).save(commit=False)

        file: InMemoryUploadedFile | None = self.cleaned_data.get("file_upload")

        if file:
            with file.open() as f:
                instance.file = f.read()

        if commit:
            instance.save()

        return instance

    class Meta:
        model = ImageFile
        fields = ["file_upload"]

admin.py:

@admin.register(ImageFile)
class ImageFileAdmin(admin.ModelAdmin):
    list_display = ["id"]
    form = DocumentFileForm
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.