在 DRF 中使用可写嵌套序列化程序时,存在验证最终唯一字段和阻止更新父序列化程序的已知问题。这个问题已被多次问到,例如:
- Unique validation on nested serializer on Django Rest Framework
- Django rest framework not creating object with FK to a model with unique=True field
为简单起见,我们以第一个问题为例:
class GenreSerializer(serializers.ModelSerializer):
class Meta:
fields = ('name',) #This field is unique
model = Genre
extra_kwargs = {
'name': {'validators': []},
}
class BookSerializer(serializers.ModelSerializer):
genre = GenreSerializer()
class Meta:
model = Book
fields = ('name', 'genre')
def create(self, validated_data):
# implement creating
def update(self, instance, validated_data):
# implement updating
现在的问题是唯一性验证在创建时也被删除了。这可以在 View 中被拦截,例如:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer = BookSerializer
def perform_create(self):
# implement logic and raise ValidationError
但是这感觉不太对,因为我们正在验证 BookViewSet
中 Genre
的唯一性。
另一种选择是在 BookSerializer
的 create()
方法中实现验证,如第二个问题中所述(参见上面的列表)。
在这两种解决方案中,我真正想念的是验证错误未附加到模型 Genre
的字段 name
并且表单中的用户输入丢失。
我想要的是将 Genre.name
的验证错误添加到现有的验证错误中,保留用户输入,并且这样做只是为了创建,而不是为了更新。
我的想法是这样的:
class GenreSerializer(serializers.ModelSerializer):
# ...
def validate_name(self, value):
# is it possible to check here if it is create or update?
if create: # this is a placeholder for the logic
if self.Meta.model.objects.filter(name=value).exists():
raise ValidationError('A genre with this name already exists.')
return value
# or to override the __init__ method
def __init__(self, *args, **kwargs):
super(GenreSerializer, self).__init__(*args, **kwargs)
# check if create or update
if create:
self.fields['name'].validators.append('validation logic')
这是否可能或是否有任何其他方法可以实现上述目标 - 在创建新实例时保留用户输入并将附加到字段 name
的验证错误添加到现有验证错误列表中?
最佳答案
我是这样做的:
class GenreSerializer(serializers.ModelSerializer):
# ... snip ...
def validate_name(self, value):
if self.context['request']._request.method == 'POST':
if self.Meta.model.objects.filter(name=value).exists():
raise ValidationError('A genre with this name already exists.')
return value
以这种方式,只有在创建新的 Genre
对象时(POST
)才会触发验证,而不是在更新对象时(PUT
) ).
创建新的 Book
对象时,Genre
的验证会传播到嵌套的序列化程序。
验证后保留所有表单输入,错误消息附加到字段 name
。
这实际上满足了我的所有标准。尽管我不觉得这是正确的做法。我仍然想知道如何在 validate_name
中手动调用 UniqueValidator
,而不是重新发明该验证。
编辑:
我找到了一种在方法中调用 UniqueValidator
的方法:
def validate_name(self, value):
if self.context['request']._request.method == 'POST':
unique = UniqueValidator(
self.Meta.model.objects.all(),
message='Genre with this name already exists.'
)
unique.set_context(self.fields['name'])
unique(value)
return value
编辑(2022-09-09):
与 self.context['request']._request.method
相比,使用 self.context['request'].method
更简单、更清晰。
使用 Django REST Framework 3.13 进行了尝试和测试。
关于python - DRF : Validate nested serializer data when creating, 但更新时没有,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48267017/