我需要将 PNG 文件作为 blob/二进制存储在数据库中,然后能够检索并显示它们。
这是我的模型,它有一个二进制字段来存储图像:
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
编码的数据无法正确压缩。
使用我的个人资料图片的示例输出:
<!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>
修复模型和视图:
导入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图像。你可以 使用 Pillow 等 Python 库来验证文件类型 上传。
压缩:
首选存储原始二进制数据(而不是预编码的 Base64) 为了效率。 Base64 编码增加存储大小 大约 33%,所以最好只在 检索阶段。