cez*_*zar 2 python django django-rest-framework
在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
Run Code Online (Sandbox Code Playgroud)
现在问题是,唯一性验证也被删除以用于创建.这可以在视图中截获,例如:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer = BookSerializer
def perform_create(self):
# implement logic and raise ValidationError
Run Code Online (Sandbox Code Playgroud)
然而,这感觉不太对,因为我们正在验证Genrein 的唯一性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')
Run Code Online (Sandbox Code Playgroud)
这有可能还是有其他方法来实现前面提到的目标 - 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
Run Code Online (Sandbox Code Playgroud)
通过这种方式,仅在Genre创建新对象(POST)时才触发验证,而不是在更新(PUT)时触发验证.创建
新Book对象时,验证Genre将传播到嵌套的序列化程序.
验证后将保留所有表单输入,并将错误消息附加到字段name.
这实际上符合我的所有标准.虽然我不觉得这是正确的做法.我仍然想知道如何手动调用UniqueValidatorin 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
Run Code Online (Sandbox Code Playgroud)