pythondjangoserializationdjango-rest-frameworkutf-8

UnicodeDecodeError: 'UTF-8' codec can't decode byte 0xff in position 0: invalid start byte. DRF


There is a backend on django and a postgres DB that stores data about the item, including pictures. And there is a problem with getting a picture from the database, i've looked at enough solutions, but none of them work.

serializers.py

import base64
import uuid
import imghdr

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


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)

full error traceback

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

Maybe the problem is with the desirealizer, but if so I don't know how to fix it.


Solution

  • The error you're encountering is due to how the image data is handled in your serializer and view. The issue is that when you're trying to serialize the image field for the response, it's attempting to decode the binary image data as UTF-8 text, which is not correct. Let's modify your code to handle this properly

    Serializers.py

    from rest_framework import serializers
    from django.core.files.base import ContentFile
    import base64
    import uuid
    import imghdr
    
    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):
            if not value:
                return None
            return value.url  # Return the URL of the image instead of the file object
    
    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):
            return CreateItem.objects.create(**validated_data)
    
    

    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):
        def get(self, request):
            items = CreateItem.objects.all()
            serializer = CreateItemSerializer(items, many=True)
            return Response({'items': serializer.data}, status=status.HTTP_200_OK)