Django REST Framework에서 multipart / form-data를 사용하여 여러 이미지 및 중첩 된 json 업로드

Metehan Gülaç

viewset에서 request.data 를 구문 분석하는 데 문제가 있습니다. 상품에 따라 여러 이미지를 추가 할 수있는 모델이 있습니다.

들어오는 데이터에서 이미지 를 분할하고 제품 데이터 를 ProductSerializer 로 보낸 다음 제품 데이터함께 이미지 를 직렬 변환기로 보내고 저장하고 싶습니다 .

다음과 같은 두 가지 모델이 있습니다.

def Product(models.Model):
    name = models.CharField(max_length=20)
    color = models.ForeignKey(Color, on_delete=models.CASCADE)

def Color(models.Model):
    name = models.CharField(max_length=15)

def ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='product_pics/')

제품 (127.0.0.1:8000/products/) 에 보내려는 요청 은 다음과 같습니다.

{
    "name": "strawberry",
    "color": {
        "name": "red"
    },
    "productimage_set": [
        {"image": "<some_encode_image_data>"}
    ]
}

serializer에는 특별한 것이 없으며 태그 링크를 추출하기 때문에 작성하지 않았습니다. multipart / form-data 를 어떻게 보내고 뷰셋에서 어떻게 파싱 할 수 있습니까? 또는 해결책은 무엇입니까?

Metehan Gülaç

솔루션을 개발했습니다. Postman을 사용하여 여러 이미지, 단일 및 중첩 데이터가 포함 된 multipart / form-data를 보냈습니다.

내 모델 파일에서 Tags 모델을 ManyToManyField로 추가하고 django-taggit도 추가했습니다. 양식 데이터 는 그림과 같습니다.

여기에 이미지 설명 입력

models.py

class Product(models.Model):
    name = models.CharField(max_length=20, blank=True)
    tags = models.ManyToManyField(Tags)
    taggit = TaggableManager(blank=True)

class ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='image_path/', null=True, blank=True)

class Tags(models.Model):
    name = models.CharField(max_length=15, blank=True)

먼저 첫 번째 것들; 첫 번째 데이터가 올바르게 구문 분석되지 않았습니다. 이에 대한 해결책과 그 대답 의 도움 으로이 사용자 정의 파서를 만들었습니다 .

class MultipartJsonParser(parsers.MultiPartParser):

    def parse(self, stream, media_type=None, parser_context=None):
        result = super().parse(
            stream,
            media_type=media_type,
            parser_context=parser_context
        )
        data = {}

        for key, value in result.data.items():
            if type(value) != str:
                data[key] = value
                continue
            if '{' in value or "[" in value:
                try:
                    data[key] = json.loads(value)
                except ValueError:
                    data[key] = value
            else:
                data[key] = value
        return parsers.DataAndFiles(data, result.files)

이제이 파서와 Django REST 내장 JSONParser로 데이터를 파싱 할 수 있습니다. 이제 뷰셋 을 구축 할 입니다.

class ProductViewSet(ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    parser_classes = [MultipartJsonParser, JSONParser]

    def get_serializer_context(self):
        context = super(ProductViewSet, self).get_serializer_context()

        # appending extra data to context
        if len(self.request.FILES) > 0:
            context.update({
                'included_images': self.request.FILES
            })

        return context

    def create(self, request, *args, **kwargs):
        # Validating images with its own serializer, but not creating.
        # The adding process must be through Serializer.
        try:
            image_serializer = ProductImageSerializer(data=request.FILES)
            image_serializer.is_valid(raise_exception=True)
        except Exception:
            raise NotAcceptable(
                detail={
                    'message': 'Upload a valid image. The file you uploaded was either not '
                               'an image or a corrupted image.'}, code=406)

        # the rest of method is about the product serialization(with extra context), 
        # validation and creation.
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

class ProductImageViewSet(ModelViewSet):
    queryset = ProductImage.objects.all()
    serializer_class = ProductImageSerializer

class TagsViewSet(ModelViewSet):
    queryset = Tags.objects.all()
    serializer_class = TagsSerializer

여기서 살펴 보겠습니다. 댓글에서 언급했듯이 이미지 파일은 request.FILES에 포함됩니다. 이러한 이유로 먼저 데이터를 ProductImageSerializer로 보내고 유효성을 검사했습니다. 유효성 검사 오류가 발생하면 프로세스가 중지되고 API가 오류 메시지를 응답으로 보냅니다. 그런 다음 get_serializer_context 메서드 의 컨텍스트에 추가 한 그림 정보와 함께 데이터를 ProductSerializer로 보냈습니다 .

우리는 create 메소드로 끝났고 다른 세부 사항은 코드에 기록됩니다.

마지막으로 serializer.py

from django.forms import ImageField as DjangoImageField

class TagsSerializer(HyperlinkedModelSerializer):
    class Meta:
    model = Tags
    fields = ['url', 'pk', 'name']

class ProductImageSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = ProductImage
        fields = ['url', 'pk', 'product', 'image']
        # attention!!! if you not use this bottom line,
        # it will show error like "product required" and
        # indirectly our validation at ProductViewSet will raise error.
        extra_kwargs = {
            'product': {'required': False}
        }
    # we created Object-level custom validation because validation not working correctly.
    # when ProductImageSerializer get single image, everything just fine but
    # when it get multiple image, serializer is just passing all the files.
    def validate(self, attrs):
        default_error_messages = {
            'invalid_image':
                'Upload a valid image. The file you uploaded was either not an image or a corrupted image.',
        }
        # in here we're verifying image with using django.forms; Pillow not necessary !!
        for i in self.initial_data.getlist('image'):
            django_field = DjangoImageField()
            django_field.error_messages = default_error_messages
            django_field.clean(i)
        return attrs

class ProductSerializer(HyperlinkedModelSerializer, TaggitSerializer):
    tags = TagsSerializer(allow_null=True, many=True, required=False)
    # you can delete this line. If you delete it, it will appear as url in response.
    productimage_set = ProductImageSerializer(allow_null=True, many=True, required=False)
    taggit = TagListSerializerField(allow_null=True, required=False)

    class Meta:
        model = Product
        fields = ['url', 'pk', 'name', 'tags', 'taggit', 'productimage_set']

    def create(self, validated_data):
        # create product
        try:
            product_obj = Product.objects.create(
                name=validated_data['name']
            )
        except Exception:
            raise NotAcceptable(detail={'message': 'The request is not acceptable.'}, code=406)

        if 'included_images' in self.context:  # checking if key is in context
            images_data = self.context['included_images']
            for i in images_data.getlist('image'):
                ProductImage.objects.create(
                    product=product_obj,
                    image=i
                )

        # pop taggit and create
        if 'taggit' in validated_data:
            taggit_data = validated_data.pop('taggit')
            for taggit_data in taggit_data:
                taggit_obj, created = Tag.objects.get_or_create(name=taggit_data)
                product_obj.taggit.add(taggit_obj)

        # pop tags and create
        if 'tags' in validated_data:
            tags_data = validated_data.pop('tags')
            for tags_data in tags_data:
                for i in tags_data.items():
                    tags_obj, created = Tags.objects.get_or_create(name=i[1])
                    product_obj.tags.add(tags_obj)

        return product_obj

그래서 여기서 무슨 일이 일어 났습니까? 이미지에 대한 추가 유효성 검사를 만든 이유는 무엇입니까? 이유를 모르겠지만 ImageSerializer는 단일 파일에 대해서만 올바른 유효성 검사를 수행합니다. 두 개의 파일을 업로드하려고하면 사진 옆에 동영상을 넣을 수도 있으며 유효성 검사가 작동하지 않습니다. 이를 방지하기 위해 내장 된 django 형식을 사용하여 순서대로 그림의 유효성을 검사합니다. .mp3의 형식을 변경하고 .jpg로 만들고 높은 크기의 파일을 업로드 해보십시오. 아무것도 작동하지 않습니다. 검증을하는 것은 순수한 장고입니다. 기타 세부 사항은 코드에 있습니다.

내가 말한대로 모든 작업을 수행하면 응답은 다음과 같습니다.

여기에 이미지 설명 입력

이것은 대부분의 Postman 사용자를 행복하게 할 것이라고 생각합니다 . 도움이되기를 바랍니다. 눈에 띄는 것이 있으면 댓글로 만나요.

이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.

침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제

에서 수정
0

몇 마디 만하겠습니다

0리뷰
로그인참여 후 검토

관련 기사

Related 관련 기사

뜨겁다태그

보관