如何使用Django Rest框架更新OneToOneField
我正在尝试从移动客户端通过RESTAPI上传图像。我已经设法使用对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
以下是我实现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
视图上实现put方法,但给出了相同的错误HotelPhotoUpload
- 我考虑在序列化程序上覆盖
方法,但我不知道是否需要在照片本身上验证任何内容。我希望这个框架能帮我解决这个问题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版本?