在DRF中使用可写嵌套序列化程序时,验证最终唯一字段并阻止更新父序列化程序存在已知问题。在这样的问题中已经多次询问过这个问题:
为简单起见,我们以第一个问题为例:
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
现在的问题是,唯一性验证也会被删除以进行创建。这可以在视图中截获,例如:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer = BookSerializer
def perform_create(self):
# implement logic and raise ValidationError
然而,这感觉不太正确,因为我们正在验证Genre
中BookViewSet
的唯一性。
另一种选择是在create()
BookSerializer
方法中实施验证,如第二个问题中所述(参见上面的列表)。
我在两个解决方案中真正错过的是验证错误未附加到模型name
的字段Genre
,并且表单中的用户输入丢失。
我想要的是将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
附加的验证错误添加到现有验证错误列表中?
答案 0 :(得分:6)
我就这样做了:
Genre
通过这种方式,仅在创建新POST
对象(PUT
)时触发验证,而不是在更新(Book
)时触发验证。
创建新的Genre
对象后,name
的验证将传播到嵌套的序列化程序。
验证后将保留所有表单输入,并将错误消息附加到字段UniqueValidator
。
这实际上符合我的所有标准。虽然我不觉得这是正确的做法。我仍然想知道如何在validate_name
中手动调用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
的方法:
domain.com/sign-in
domain.com/cookie-policy
答案 1 :(得分:1)
我在两个解决方案中真正错过的是验证错误未附加到模型类型的字段名称,并且表单中的用户输入丢失。
这是直截了当的:
from rest_framework import exceptions
class BookViewSet(viewsets.ModelViewSet):
....
def perform_create(self, serializer):
if check_failed():
raise exceptions.ValidationError(
exceptions._get_error_details({
'genre': {
'name': ['must be unique']
}
})
)
答案 2 :(得分:0)
除了@cezar的答案外,我想知道如何不仅根据条件进行覆盖,而且要完全覆盖和忽略任何字段。
通过覆盖to_internal_value
的ParentSerialzer,可以“跳过”子序列化程序验证,或编写自定义序列化程序验证以覆盖子序列化程序:
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True)
def to_internal_value(self, *args, **kwargs):
try:
# runs the child serializers
return super().to_internal_value(*args, **kwargs)
except ValidationError as e:
# fails, and then overrides the child errors with the parent error
return self.validate(self.initial_data)
def validate(self, attrs):
errors = {}
errors['custom_override_error'] = 'this ignores and overrides the children serializer errors'
if len(errors):
raise ValidationError(errors)
return attrs
class Meta:
model = Parent