Как использовать Django BinaryField для хранения PNG-изображений в postgres?

Мне нужно хранить файлы PNG в виде blob/binary в базе данных, а затем иметь возможность извлекать и показывать их.

Вот моя модель, которая имеет двоичное поле для хранения изображения:

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

Я создал виджет, основанный на этом ответе:

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()

И я использовал его в admin.py вот так:

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

Затем я кодирую файл как base64 в представлении:

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>

Ошибок нет, данные отображаются в элементе pre, но элемент img не может загрузить изображение. Я пробовал использовать некоторые онлайн-конвертеры, чтобы проверить правильность данных, но они выдают ошибку, говоря, что строка base64 недействительна.

Он загружается правильно, если я конвертирую файл в base64 в виджете перед сохранением его в базе данных (return base64.b64encode(upload.read()).decode('ascii') вместо return upload.read()), а затем снова конвертирую его в представлении. Но я не могу использовать этот метод, потому что мне может понадобиться сжать файлы, и, очевидно, base64 закодированные данные не могут быть сжаты должным образом.

Исправьте в модели и представлении:

  1. Убедитесь, что BinaryField правильно обрабатывает необработанные двоичные данные: Убедитесь что BinaryField в вашей модели хранит непосредственно необработанные двоичные данные, а не какие-либо манипулированные или предварительно закодированные данные.
  2. Обновите представление, чтобы оно правильно обрабатывало кодировку Base64: Убедитесь, что что двоичные данные, полученные из базы данных, правильно закодированы в виде правильной строки Base64.

import base64

from django.shortcuts import render

from .models import ImageFile
def image_view(request, id):

document_file = ImageFile.objects.filter(id=id).first()
data = ""
if document_file and document_file.file:  # Ensure `file` is not None
    data = base64.b64encode(document_file.file).decode('utf-8')
return render(request, "image.html", {"data": data})

Исправление в HTML-шаблоне

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Image Viewer</title>
  </head>
  <body>
    {% if data %}
      <img src="data:image/png;base64,{{ data }}" alt="Image" />
    {% else %}
      <p>No image data available.</p>
    {% endif %}
  </body>
</html>
  1. Хранение двоичных полей:

    models.BinaryField хранит необработанные двоичные данные, поэтому, когда вы вызываете upload.read() в вашем виджете, убедитесь, что данные не были изменены (например, путем частичного чтения или кодирования).

  2. Кодировка Base64:

    Кодирование Base64 должно происходить во время рендеринга изображения в браузере, а не перед сохранением данных в базе данных. Файл

  3. Проверка:

    Убедитесь, что загруженный файл действительно является действительным PNG-изображением. Вы можете использовать библиотеки Python, такие как Pillow, для проверки типа файла во время загрузки. загрузки.

  4. Сжатие:

    Хранение необработанных двоичных данных (вместо предварительно закодированных Base64) предпочтительнее для эффективности. Кодирование Base64 увеличивает размер хранилища примерно на примерно на 33 %, поэтому лучше кодировать только на этапе на этапе извлечения.

В итоге я создал пользовательскую форму (вместо виджета), которая считывает файл из FileField и затем сохраняет его в модели.

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
Вернуться на верх