[Django] base64 인코딩된 이미지 ImageField로 저장

Cherry·2022년 4월 4일
2
post-thumbnail
post-custom-banner

그전에 포스트에서 말했다시피 대량의 json데이터들과 이미지를 어떻게 전송을 해야 할지 고민을 많이 했다. 이미지와 json데이터들을 따로 보내는 방식으로도 개발해었는데 결국 최종적으로는 base64로 이미지를 인코딩하여 스트링 형태로 만들어줘서 json객체에 담아서 한꺼번에 보내는 방식을 선택하기로 했다. 우선은 DRF을 사용하여 개발했기 때문에 모델을 먼저 정의해줬다.

Models.py

class ProductImg(BaseModel):

    product = models.ForeignKey(Product,on_delete=models.CASCADE, related_name='images')
    image = models.ImageField(upload_to="", blank=True)

어떠한 프로덕트에 대한 이미지로 프로덕트라는 모델과 ForeignKey로 연결해주었고 밑에 이미지필드를 사용해주어서 이미지를 저장할 수 있는 필드를 만들어주었다.

Serializers.py

class DecodeSerializer(serializers.ModelSerializer) :


    def create(self, validated_data):
        product = Product.objects.create(**validated_data)

		#이미지 디코딩하기 
        bulk_list = []
        num = 1
        for image_string in self.context.get("images"): #base64로 인코딩된 이미지들을 불러온다.
            header, data = image_string.split(';base64,')
            data_format, ext = header.split('/')
            try:
                image_data = base64.b64decode(data)
                image_root = settings.MEDIA_ROOT + '\\' + str(product.id) + "_" + str(num) + "." + ext
                with open(image_root, 'wb') as f:
                    f.write(image_data)
                    bulk_list.append(ProductImg(product=product, image=f'{product.id}_{num}.{ext}'))
                num += 1
            except TypeError:
                self.fail('invalid_image')

        images = ProductImg.objects.bulk_create(bulk_list)

        return product

    class Meta:
        model = Product
        fields = '__all__'

코드를 설명 하자면 우선 이미지가 어떤식으로 인코딩되어서 오는지를 살펴보아야 한다.
이미지는 전체적으로 다음과 같이 전송되어 온다

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnsAAALWCAYAAAA+tlWnAAAgAElEQVR4nOzdd5gcV5Xw4d+9VZ27p7sn56A0o5wtR5wJBhYbY/Lukj4wwWDSsrAYMHlZYMmYJSw ...생략...

header, data = image_string.split(';base64,')

  • 위와 같이 전송되어 오기 때문에 data:image/png;base64,와 뒤에 string을 분리해주어야 한다. 따라서 위코드를 통해서 분리해준다. 따라서 header에는 data:image/png가 들어 있고 뒤에 data에는 base64로 인코딩된 이미지가 들어있다.

data_format, ext = header.split('/')

  • 후에 디코딩후에 데이터베이스에 저장할 때 이미지의 확장자가 어떤 확장자인지 알기 위해서 를 통해 분리해준다. ext에 파일 확장자, png가 들어가게 된다.
try:
	image_data = base64.b64decode(data)
    image_root = settings.MEDIA_ROOT + '\\' + str(product.id) + "_" + str(num) + "." + ext
    with open(image_root, 'wb') as f:
    	f.write(image_data)
        bulk_list.append(ProductImg(product=product, image=f'{product.id}_{num}.{ext}'))
    num += 1
except TypeError:
	self.fail('invalid_image')
  • 우선 try-catch으로 이미지가 제대로 된 이미지 인지 확인해준다
  • 만약 제대로 된 이미지라면 mage_data = base64.b64decode(data)를 통해 인코딩된 이미지가 다시 디코딩될 것이다.
  • image_root는 이미지가 저장되는 경로와 저장되는 이름을 정의해준다
  • 이후 아래코드는 image_root로 디코딩된 이미지를 저장시켜준다.

images = ProductImg.objects.bulk_create(bulk_list)

  • 마지막으로 bulk_create을 이용해주어서 이미지를 만들어준다.

Views.py

위에 시리얼라이저에서 create을 오버라이딩해서 잘 구현해주었기 때문에 뷰에는 딱히 더해줄 코드가 없고 단순하다.

class APITest(APIView):

    def post(self, request) :

        data = request.data
        #serializer 직렬화
        serializer = DecodeSerializer(data=data, context={'request': request, 'images':data.get("images")})

        # 데이터 유효성 검사
        if serializer.is_valid():
            # DB에 저장
            serializer.save()
            return Response(get_successCode(serializer.data), status=status.HTTP_201_CREATED)

        return Response(get_failCode(serializer.errors), status=status.HTTP_400_BAD_REQUEST)

실제로 테스트해보면 잘 저장되는 것을 볼 수 있다. 끝~!!🥰

post-custom-banner

0개의 댓글