Как использовать 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
закодированные данные не могут быть сжаты должным образом.
Исправьте в модели и представлении:
- Убедитесь, что BinaryField правильно обрабатывает необработанные двоичные данные: Убедитесь что BinaryField в вашей модели хранит непосредственно необработанные двоичные данные, а не какие-либо манипулированные или предварительно закодированные данные.
- Обновите представление, чтобы оно правильно обрабатывало кодировку 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>
Хранение двоичных полей:
models.BinaryField хранит необработанные двоичные данные, поэтому, когда вы вызываете upload.read() в вашем виджете, убедитесь, что данные не были изменены (например, путем частичного чтения или кодирования).
Кодировка Base64:
Кодирование Base64 должно происходить во время рендеринга изображения в браузере, а не перед сохранением данных в базе данных. Файл
Проверка:
Убедитесь, что загруженный файл действительно является действительным PNG-изображением. Вы можете использовать библиотеки Python, такие как Pillow, для проверки типа файла во время загрузки. загрузки.
Сжатие:
Хранение необработанных двоичных данных (вместо предварительно закодированных 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