You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
问题分析
  1. 模型管理器拼写错误:CreateItem.object.all()应为CreateItem.objects.all(),少了复数s,会导致查询逻辑异常。
  2. 图片序列化逻辑错误:视图中直接将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:35:01