如何使 PATCH 方法适用于 Django 应用程序中的部分更新?没有 ImageField 更新,它不起作用

How to make PATCH method work for partial update in Django app? Without ImageField update, it doesn't work

我正在尝试在 Django 网络应用程序中启用部分更新(使用 Django Rest Framework)。

我要更新的模型包括 ImageField。
PATCH 方法在我更新 ImageField 时有效,但在我不更新 ImageField 时无效。
错误消息说
AttributeError:'NoneType' object has no attribute 'items'

我该如何解决?

型号:

class Work(models.Model):
    owner       = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True)
    title       = models.CharField(max_length=120)
    made_date   = models.DateField(default=datetime.date.today, null=True, blank=True)
    note        = models.TextField(max_length=2000, null=True, blank=True)
    image       = models.ImageField(upload_to='work_pic', default='default_image.png')

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if self.image and self.image.name.lower().endswith(('.jpg', '.jpeg')):
            pilImage = Img.open(BytesIO(self.image.read()))
            for orientation in ExifTags.TAGS.keys():
                if ExifTags.TAGS[orientation] == 'Orientation':
                    break
            exif = dict(pilImage._getexif().items())

            if exif[orientation] == 3:
                pilImage = pilImage.rotate(180, expand=True)
            elif exif[orientation] == 6:
                pilImage = pilImage.rotate(270, expand=True)
            elif exif[orientation] == 8:
                pilImage = pilImage.rotate(90, expand=True)

            output = BytesIO()
            pilImage.save(output, format='JPEG', quality=75)
            output.seek(0)
            self.image = File(output, self.image.name)

        return super(Work, self).save(*args, **kwargs)

序列化器:

class WorkSerializer(serializers.ModelSerializer):
    owner = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = Work
        fields = '__all__'

    def create(self, validated_data):
        return Work.objects.create(**validated_data)

视图集:

class WorkViewSet(viewsets.ModelViewSet):
    queryset = Work.objects.all()
    serializer_class = WorkSerializer

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

发送请求的方法(Vue.js):

updateWork: function() {
    let formData  = new FormData();

    //Initial value of currentWork.image is text (path to the original image in the media folder). 
    //When user upload new image, the file is set to currentWork.image. So condition is match to the if statement bellow.
    if (this.currentWork.image instanceof File) {
        formData.append("image", this.currentWork.image);
    }

    formData.append("title", this.currentWork.title);
    formData.append("made_date", this.currentWork.made_date);
    formData.append("note", this.currentWork.note);
    this.loading = true;
    axios.patch(`/api/work/${this.currentWork.id}/`, formData, {
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    })
        .then((response) => {
            this.loading = false;
        })
        .catch((err) => {
            this.loading = false;
            console.log(err);
        })

错误信息:

Internal Server Error: /api/work/61/    
Traceback (most recent call last):  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 35, in inner  
    response = get_response(request)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/django/core/handlers/base.py", line 128, in _get_response    
    response = self.process_exception_by_middleware(e, request)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/django/core/handlers/base.py", line 126, in _get_response  
    response = wrapped_callback(request, *callback_args, **callback_kwargs)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view  
    return view_func(*args, **kwargs)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/viewsets.py", line 103, in view  
    return self.dispatch(request, *args, **kwargs)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/views.py", line 483, in dispatch  
    response = self.handle_exception(exc)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/views.py", line 443, in handle_exception  
    self.raise_uncaught_exception(exc)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/views.py", line 480, in dispatch  
    response = handler(request, *args, **kwargs)  
  File "/Users/rami/Dev/MySweetsBase/works/viewsets.py", line 13, in partial_update  
    return self.update(request, *args, **kwargs)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/mixins.py", line 70, in update  
    self.perform_update(serializer)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/mixins.py", line 80, in perform_update  
    serializer.save()  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/serializers.py", line 209, in save  
    self.instance = self.update(self.instance, validated_data)  
  File "/Users/rami/Dev/MySweetsBase/myvenv/lib/python3.6/site-packages/rest_framework/serializers.py", line 981, in update  
    instance.save()  
  File "/Users/rami/Dev/MySweetsBase/works/models.py", line 26, in save   
    exif = dict(pilImage._getexif().items())  
AttributeError: 'NoneType' object has no attribute 'items'  
class Work(models.Model):
    owner       = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True)
    title       = models.CharField(max_length=120)
    made_date   = models.DateField(default=datetime.date.today, null=True, blank=True)
    note        = models.TextField(max_length=2000, null=True, blank=True)
    image       = models.ImageField(upload_to='work_pic', default='default_image.png')

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):

        if not self.id:
            # add
            self.set_image()
        else:
            # change
            this = Work.objects.get(id=self.id)
            if this.image != self.image:
                self.set_image()
        return super(Work, self).save(*args, **kwargs)

    def set_image(self):
        if self.image and self.image.name.lower().endswith(('.jpg', '.jpeg')):
            pilImage = Img.open(BytesIO(self.image.read()))
            for orientation in ExifTags.TAGS.keys():
                if ExifTags.TAGS[orientation] == 'Orientation':
                    break
            exif = dict(pilImage._getexif().items())

            if exif[orientation] == 3:
                pilImage = pilImage.rotate(180, expand=True)
            elif exif[orientation] == 6:
                pilImage = pilImage.rotate(270, expand=True)
            elif exif[orientation] == 8:
                pilImage = pilImage.rotate(90, expand=True)

            output = BytesIO()
            pilImage.save(output, format='JPEG', quality=75)
            output.seek(0)
            self.image = File(output, self.image.name)

试一试