Django DRF从PostgreSQL获取图片时出现UTF-8解码错误求助
问题描述
使用Django搭建后端,基于PostgreSQL存储含图片的商品数据,从数据库获取图片时触发UnicodeDecodeError,无法将图片二进制数据解码为UTF-8。
相关代码
serializers.py
import base64 import uuid import imghdr from django.core.files.base import ContentFile from rest_framework import serializers from .models import CreateItem class Base64ImageField(serializers.ImageField): def to_internal_value(self, data): if isinstance(data, str) and 'data:' in data and ';base64,' in data: header, data = data.split(';base64,') try: decoded_file = base64.b64decode(data) except (TypeError, ValueError): self.fail('invalid_image') file_name = str(uuid.uuid4())[:12] file_extension = self.get_file_extension(file_name, decoded_file) complete_file_name = f"{file_name}.{file_extension}" data = ContentFile(decoded_file, name=complete_file_name) return super(Base64ImageField, self).to_internal_value(data) def get_file_extension(self, file_name, decoded_file): extension = imghdr.what(file_name, decoded_file) return extension or 'jpg' class CreateItemSerializer(serializers.ModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') photo = Base64ImageField(required=True) class Meta: model = CreateItem fields = '__all__' def create(self, validated_data): items = CreateItem.object.create_item( name = validated_data.get('name'), price = validated_data.get('price'), description = validated_data.get('description'), type_item = validated_data.get('type_item'), photo=validated_data.get('photo') ) return items
views.py
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .models import CreateItem from .serializers import CreateItemSerializer class GetItemView(APIView): serializer_class = CreateItemSerializer def get(self, request): items = CreateItem.object.all() response_data = { 'items': [ { 'photo': item.photo, 'name': item.name, 'description': item.description, 'type_item': item.type_item, 'price': item.price, } for item in items ] } return Response(response_data, status=status.HTTP_200_OK)
错误栈
Internal Server Error: /api/v1/item/items-get/ Traceback (most recent call last): File "/home/anton/Documents/e-m/e-market/backend/my_backend/venv_em/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) ^^^^^^^^^^^^^^^^^^^^^ File "/home/anton/Documents/e-m/e-market/backend/my_backend/venv_em/lib/python3.12/site-packages/django/core/handlers/base.py", line 220, in _get_response response = response.render() ^^^^^^^^^^^^^^^^^ File "/home/anton/Documents/e-m/e-market/backend/my_backend/venv_em/lib/python3.12/site-packages/django/template/response.py", line 114, in render self.content = self.rendered_content ^^^^^^^^^^^^^^^^^^^^^ File "/home/anton/Documents/e-m/e-market/backend/my_backend/venv_em/lib/python3.12/site-packages/rest_framework/response.py", line 74, in rendered_content ret = renderer.render(self.data, accepted_media_type, context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/anton/Documents/e-m/e-market/backend/my_backend/venv_em/lib/python3.12/site-packages/rest_framework/renderers.py", line 100, in render ret = json.dumps( ^^^^^^^^^^^ File "/home/anton/Documents/e-m/e-market/backend/my_backend/venv_em/lib/python3.12/site-packages/rest_framework/utils/json.py", line 25, in dumps return json.dumps(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/json/__init__.py", line 238, in dumps **kw).encode(obj) ^^^^^^^^^^^ File "/usr/lib/python3.12/json/encoder.py", line 200, in encode chunks = self.iterencode(o, _one_shot=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/json/encoder.py", line 258, in iterencode return _iterencode(o, 0) ^^^^^^^^^^^^^^^^^ File "/home/anton/Documents/e-m/e-market/backend/my_backend/venv_em/lib/python3.12/site-packages/rest_framework/utils/encoders.py", line 52, in default return obj.decode() ^^^^^^^^^^^^ UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte [04/Oct/2024 18:59:30] "GET /api/v1/item/items-get/ HTTP/1.1" 500 115134
问题分析
- 模型管理器拼写错误:
CreateItem.object.all()应为CreateItem.objects.all(),少了复数s,会导致查询逻辑异常。 - 图片序列化逻辑错误:视图中直接将
item.photo(Django的ImageField对象)放入响应数据,DRF的JSON编码器会尝试将图片二进制内容解码为UTF-8字符串,但图片属于二进制数据,并非UTF-8文本,因此触发解码错误。
解决方案
方案1:使用序列化器处理响应(推荐,返回图片URL)
修复视图拼写错误,利用DRF序列化器自动处理图片字段,返回可直接访问的图片URL(需提前配置MEDIA_URL和MEDIA_ROOT):
修改views.py:
class GetItemView(APIView): serializer_class = CreateItemSerializer def get(self, request): items = CreateItem.objects.all() # 修复拼写错误 serializer = self.serializer_class(items, many=True) return Response({'items': serializer.data}, status=status.HTTP_200_OK)
方案2:返回Base64编码的图片
如果需要直接返回Base64格式的图片,修改Base64ImageField,添加to_representation方法实现编码逻辑:
修改serializers.py中的Base64ImageField:
class Base64ImageField(serializers.ImageField): def to_internal_value(self, data): # 原代码保留 if isinstance(data, str) and 'data:' in data and ';base64,' in data: header, data = data.split(';base64,') try: decoded_file = base64.b64decode(data) except (TypeError, ValueError): self.fail('invalid_image') file_name = str(uuid.uuid4())[:12] file_extension = self.get_file_extension(file_name, decoded_file) complete_file_name = f"{file_name}.{file_extension}" data = ContentFile(decoded_file, name=complete_file_name) return super(Base64ImageField, self).to_internal_value(data) def get_file_extension(self, file_name, decoded_file): extension = imghdr.what(file_name, decoded_file) return extension or 'jpg' def to_representation(self, value): # 将图片编码为Base64字符串 if value and hasattr(value, 'path'): with open(value.path, 'rb') as image_file: encoded_string = base64.b64encode(image_file.read()).decode('utf-8') ext = value.name.split('.')[-1] if '.' in value.name else 'jpg' return f'data:image/{ext};base64,{encoded_string}' return None
额外修复
CreateItemSerializer的create方法中,CreateItem.object.create_item需改为CreateItem.objects.create_item(若create_item是自定义模型管理器方法),或直接使用CreateItem.objects.create(**validated_data)。
注意事项
- 配置Django媒体文件路径:
# settings.py MEDIA_ROOT = BASE_DIR / 'media' MEDIA_URL = '/media/' - 在项目根URL配置中添加媒体文件路由:
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # 其他路由 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
内容的提问来源于stack exchange,提问作者Anton
相关产品推荐
相关产品推荐

