如何使用Django Rest框架更新OneToOneField

如何使用Django Rest框架更新OneToOneField,django,django-rest-framework,Django,Django Rest Framework,我正在尝试从移动客户端通过RESTAPI上传图像。我已经设法使用对REST端点的多部分请求来实现它,但是当我尝试更新映像时,由于OneToOneField上的约束,请求没有得到正确处理 以下是我实现API的方式: 型号.py class Hotel(models.Model): name = models.CharField(max_length=500, null=False, blank=False) address = models.CharField(max_lengt

我正在尝试从移动客户端通过RESTAPI上传图像。我已经设法使用对REST端点的多部分请求来实现它,但是当我尝试更新映像时,由于OneToOneField上的约束,请求没有得到正确处理


以下是我实现API的方式:

型号.py

class Hotel(models.Model):
    name = models.CharField(max_length=500, null=False, blank=False)
    address = models.CharField(max_length=500, null=False, blank=False)
    rating = models.FloatField()
    owner = models.CharField(max_length=200, null=False)

    class Meta:
        ordering = ['name']


class HotelPhoto(models.Model):
    photo = models.ImageField(upload_to='hotel_photos', null=True)
    hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, primary_key=True)
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        photo_serializer = HotelPhotoSerializer(data=request.data,
                                                context={'request': request})

        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
class HotelSerializer(serializers.HyperlinkedModelSerializer):
    photo = serializers.ImageField(source='hotelphoto.photo', read_only=True)

    class Meta:
        model = Hotel
        fields = ['url', 'id', 'name', 'address', 'rating', 'owner', 'photo']


class HotelPhotoSerializer(serializers.ModelSerializer):
    photo_url = serializers.SerializerMethodField()

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo', 'photo_url']

    def get_photo_url(self, obj):
        return self.context['request'].build_absolute_uri(obj.photo.url)
# Didn't change the HotelSerializer

class HotelPhotoSerializer(serializers.ModelSerializer):
    hotel = serializers.PrimaryKeyRelatedField(
            many=False,
            queryset=Hotel.objects.all())

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo']

    def create(self, validated_data):
        # Instead of creating a new HotelPhoto instance
        # changed the photo field from the Hotel instance
        hotel = validated_data.get('hotel')
        photo = validated_data.get('photo')
        hotel.hotelphoto.photo.save(photo.name, photo)
        hotel.save()
        return hotel.hotelphoto
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        # I'm already sending the hotel id on the POST request
        photo_serializer = HotelPhotoSerializer(data=request.data)
        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
视图.py

class Hotel(models.Model):
    name = models.CharField(max_length=500, null=False, blank=False)
    address = models.CharField(max_length=500, null=False, blank=False)
    rating = models.FloatField()
    owner = models.CharField(max_length=200, null=False)

    class Meta:
        ordering = ['name']


class HotelPhoto(models.Model):
    photo = models.ImageField(upload_to='hotel_photos', null=True)
    hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, primary_key=True)
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        photo_serializer = HotelPhotoSerializer(data=request.data,
                                                context={'request': request})

        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
class HotelSerializer(serializers.HyperlinkedModelSerializer):
    photo = serializers.ImageField(source='hotelphoto.photo', read_only=True)

    class Meta:
        model = Hotel
        fields = ['url', 'id', 'name', 'address', 'rating', 'owner', 'photo']


class HotelPhotoSerializer(serializers.ModelSerializer):
    photo_url = serializers.SerializerMethodField()

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo', 'photo_url']

    def get_photo_url(self, obj):
        return self.context['request'].build_absolute_uri(obj.photo.url)
# Didn't change the HotelSerializer

class HotelPhotoSerializer(serializers.ModelSerializer):
    hotel = serializers.PrimaryKeyRelatedField(
            many=False,
            queryset=Hotel.objects.all())

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo']

    def create(self, validated_data):
        # Instead of creating a new HotelPhoto instance
        # changed the photo field from the Hotel instance
        hotel = validated_data.get('hotel')
        photo = validated_data.get('photo')
        hotel.hotelphoto.photo.save(photo.name, photo)
        hotel.save()
        return hotel.hotelphoto
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        # I'm already sending the hotel id on the POST request
        photo_serializer = HotelPhotoSerializer(data=request.data)
        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
序列化程序.py

class Hotel(models.Model):
    name = models.CharField(max_length=500, null=False, blank=False)
    address = models.CharField(max_length=500, null=False, blank=False)
    rating = models.FloatField()
    owner = models.CharField(max_length=200, null=False)

    class Meta:
        ordering = ['name']


class HotelPhoto(models.Model):
    photo = models.ImageField(upload_to='hotel_photos', null=True)
    hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, primary_key=True)
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        photo_serializer = HotelPhotoSerializer(data=request.data,
                                                context={'request': request})

        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
class HotelSerializer(serializers.HyperlinkedModelSerializer):
    photo = serializers.ImageField(source='hotelphoto.photo', read_only=True)

    class Meta:
        model = Hotel
        fields = ['url', 'id', 'name', 'address', 'rating', 'owner', 'photo']


class HotelPhotoSerializer(serializers.ModelSerializer):
    photo_url = serializers.SerializerMethodField()

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo', 'photo_url']

    def get_photo_url(self, obj):
        return self.context['request'].build_absolute_uri(obj.photo.url)
# Didn't change the HotelSerializer

class HotelPhotoSerializer(serializers.ModelSerializer):
    hotel = serializers.PrimaryKeyRelatedField(
            many=False,
            queryset=Hotel.objects.all())

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo']

    def create(self, validated_data):
        # Instead of creating a new HotelPhoto instance
        # changed the photo field from the Hotel instance
        hotel = validated_data.get('hotel')
        photo = validated_data.get('photo')
        hotel.hotelphoto.photo.save(photo.name, photo)
        hotel.save()
        return hotel.hotelphoto
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        # I'm already sending the hotel id on the POST request
        photo_serializer = HotelPhotoSerializer(data=request.data)
        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

这就是我得到的错误:

Error uploading image: {'hotel': [ErrorDetail(string='hotel photo with this hotel already exists.', code='unique')]}
Bad Request: /hotels/photo/upload/
我知道这是由于OneToOneField上的限制,因为我已经上传了一张照片,但是我应该如何做才能更新
HotelPhoto.photo
字段

我尝试过的

  • 在序列化程序上使用
    partial=True
    HotelPhotoUpload
    视图上实现put方法,但给出了相同的错误
  • 我考虑在序列化程序上覆盖
    validate
    方法,但我不知道是否需要在照片本身上验证任何内容。我希望这个框架能帮我解决这个问题
  • 考虑合并
    HotelPhoto
    Hotel
    模型,但这需要对其他代码进行大的重构

编辑

我目前正在使用Django3.0.2。 根据neferpitou的回答,我在做了这些小改动后,成功地使其工作:

序列化程序.py

class Hotel(models.Model):
    name = models.CharField(max_length=500, null=False, blank=False)
    address = models.CharField(max_length=500, null=False, blank=False)
    rating = models.FloatField()
    owner = models.CharField(max_length=200, null=False)

    class Meta:
        ordering = ['name']


class HotelPhoto(models.Model):
    photo = models.ImageField(upload_to='hotel_photos', null=True)
    hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, primary_key=True)
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        photo_serializer = HotelPhotoSerializer(data=request.data,
                                                context={'request': request})

        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
class HotelSerializer(serializers.HyperlinkedModelSerializer):
    photo = serializers.ImageField(source='hotelphoto.photo', read_only=True)

    class Meta:
        model = Hotel
        fields = ['url', 'id', 'name', 'address', 'rating', 'owner', 'photo']


class HotelPhotoSerializer(serializers.ModelSerializer):
    photo_url = serializers.SerializerMethodField()

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo', 'photo_url']

    def get_photo_url(self, obj):
        return self.context['request'].build_absolute_uri(obj.photo.url)
# Didn't change the HotelSerializer

class HotelPhotoSerializer(serializers.ModelSerializer):
    hotel = serializers.PrimaryKeyRelatedField(
            many=False,
            queryset=Hotel.objects.all())

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo']

    def create(self, validated_data):
        # Instead of creating a new HotelPhoto instance
        # changed the photo field from the Hotel instance
        hotel = validated_data.get('hotel')
        photo = validated_data.get('photo')
        hotel.hotelphoto.photo.save(photo.name, photo)
        hotel.save()
        return hotel.hotelphoto
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        # I'm already sending the hotel id on the POST request
        photo_serializer = HotelPhotoSerializer(data=request.data)
        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
视图.py

class Hotel(models.Model):
    name = models.CharField(max_length=500, null=False, blank=False)
    address = models.CharField(max_length=500, null=False, blank=False)
    rating = models.FloatField()
    owner = models.CharField(max_length=200, null=False)

    class Meta:
        ordering = ['name']


class HotelPhoto(models.Model):
    photo = models.ImageField(upload_to='hotel_photos', null=True)
    hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, primary_key=True)
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        photo_serializer = HotelPhotoSerializer(data=request.data,
                                                context={'request': request})

        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
class HotelSerializer(serializers.HyperlinkedModelSerializer):
    photo = serializers.ImageField(source='hotelphoto.photo', read_only=True)

    class Meta:
        model = Hotel
        fields = ['url', 'id', 'name', 'address', 'rating', 'owner', 'photo']


class HotelPhotoSerializer(serializers.ModelSerializer):
    photo_url = serializers.SerializerMethodField()

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo', 'photo_url']

    def get_photo_url(self, obj):
        return self.context['request'].build_absolute_uri(obj.photo.url)
# Didn't change the HotelSerializer

class HotelPhotoSerializer(serializers.ModelSerializer):
    hotel = serializers.PrimaryKeyRelatedField(
            many=False,
            queryset=Hotel.objects.all())

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo']

    def create(self, validated_data):
        # Instead of creating a new HotelPhoto instance
        # changed the photo field from the Hotel instance
        hotel = validated_data.get('hotel')
        photo = validated_data.get('photo')
        hotel.hotelphoto.photo.save(photo.name, photo)
        hotel.save()
        return hotel.hotelphoto
class HotelPhotoUpload(APIView):
    parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        # I'm already sending the hotel id on the POST request
        photo_serializer = HotelPhotoSerializer(data=request.data)
        if photo_serializer.is_valid():
            photo_serializer.save()

            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
我忘了提到的一件事是,在上传照片之前,我总是向
hotels
端点(使用
HotelSerializer
)发送请求(POST或PUT)。因此,我不希望由于不存在酒店而在
photo/upload
端点上出现问题

移动客户端

不幸的是,无法发布多部分post请求内容,因为它太大了。但下面是使用改进2.5.0的客户机方法实现

// Sends the hotel id and the entire content of the photo file.
@Multipart
@POST(UPLOAD_ENDPOINT)
fun uploadPhoto(@Part("hotel") hotelId: RequestBody,
                @Part photo: MultipartBody.Part) : Call<UploadResult>

companion object {
    const val UPLOAD_ENDPOINT = "hotels/photo/upload/"
}
//发送酒店id和照片文件的全部内容。
@多部分
@POST(上传\u端点)
有趣的上传照片(@Part(“hotel”)hotelId:RequestBody,
@部分照片:MultipartBody.Part):呼叫
伴星{
const val UPLOAD_ENDPOINT=“hotels/photo/UPLOAD/”
}

Foreignkey
OneToOneField
可以以相同的方式序列化。 这是你的
views.py

class HotelPhotoUpload(APIView):
    # parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        hotel = Hotel.objects.get(name=request.data.get('hotel'))
        request.data['hotel'] = hotel.id
        photo_serializer = HotelPhotoSerializer(data=request.data)
        # print(photo_serializer)
        if photo_serializer.is_valid():
            photo_serializer.save()
            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            # logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
class HotelSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = Hotel
        fields = '__all__'


class HotelPhotoSerializer(serializers.ModelSerializer):
    hotel = serializers.PrimaryKeyRelatedField(many=False, queryset=Hotel.objects.all())

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo',]

    def create(self, validated_data):
        hotel_photo = HotelPhoto.objects.create(**validated_data)
        hotel_photo.save()
        return hotel_photo
serializers.py

class HotelPhotoUpload(APIView):
    # parser_classes = [FormParser, MultiPartParser]

    def post(self, request):
        hotel = Hotel.objects.get(name=request.data.get('hotel'))
        request.data['hotel'] = hotel.id
        photo_serializer = HotelPhotoSerializer(data=request.data)
        # print(photo_serializer)
        if photo_serializer.is_valid():
            photo_serializer.save()
            return Response(photo_serializer.data,
                            status=status.HTTP_201_CREATED)
        else:
            # logger.error(f'Error uploading image: {photo_serializer.errors}')
            return Response(photo_serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
class HotelSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = Hotel
        fields = '__all__'


class HotelPhotoSerializer(serializers.ModelSerializer):
    hotel = serializers.PrimaryKeyRelatedField(many=False, queryset=Hotel.objects.all())

    class Meta:
        model = HotelPhoto
        fields = ['hotel', 'photo',]

    def create(self, validated_data):
        hotel_photo = HotelPhoto.objects.create(**validated_data)
        hotel_photo.save()
        return hotel_photo
我不明白为什么在你的
HotelSerializer
中有额外的字段,所以我把它删减了。如果您有特定的用例,请在代码中随意修改。而且您的
酒店
模型中没有主键,因此默认情况下,它将创建
id
字段,我假设每个
酒店名称
都是唯一的

邮递员要求:

来自管理部门的酒店数据:


谢谢你的回答,这很有帮助。现在它给了我一个关于
photo\u serializer.save()
IntegrityError
,但我相信我可以解决这个问题。在对你的答案做了一些小改动之后,它就起了作用。在
create()
方法中,我没有创建
HotelPhoto
实例,而是直接将图像保存在
Hotel
实例上,该实例由
validated\u data.get('Hotel')
使用
Hotel.HotelPhoto.photo.save(photo.name,photo)
Foreignkey
OneToOneField
使用drf可能会非常棘手,关于它的文档也不多。很高兴它至少起到了一些帮助:)如果它没有要求太多,你能用我所做的修改来调整你的答案,以便我能接受它吗?我不想在它部分工作的时候接受它,但我也不想在问题已经解决的时候不回答它。我会的,但是你用的是什么Django版本?