我从UniqeTogetherValidator 开始使用自定义验证器,并添加了nested_get 和nested_getattr 以获取嵌套对象的值。这是验证器:
from typing import Any, Dict
from functools import reduce
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
from rest_framework.utils.representation import smart_repr
from rest_framework.validators import qs_exists, qs_filter
def nested_get(dictionary: Dict, keys: str, default=None) -> Any:
"""
Apply get to a nested dict given a hierarchical key separated with '.'
"""
return reduce(
lambda d, key: d.get(key, default) if isinstance(d, dict) else default,
keys.split("."),
dictionary,
)
def nested_getattr(instance: Any, attrs: str) -> Any:
"""
Fetch an attribute value from a nested sintance given a hierarchical attrs separated with '.'
"""
return reduce(getattr, [instance] + attrs.split("."))
class UniqueTogetherRelatedValidator:
"""
Validator that corresponds to `unique_together = (...)` on a model class.
Should be applied to the serializer class, not to an individual field.
"""
message = _("The fields {field_names} must make a unique set.")
missing_message = _("This field is required.")
requires_context = True
def __init__(self, queryset, fields, message=None):
self.queryset = queryset
self.fields = fields
self.message = message or self.message
def enforce_required_fields(self, attrs, serializer):
"""
The `UniqueTogetherValidator` always forces an implied 'required'
state on the fields it applies to.
"""
if serializer.instance is not None:
return
missing_items = {
field_name: self.missing_message
for field_name in self.fields
if serializer.fields[field_name].source not in attrs
}
if missing_items:
raise ValidationError(missing_items, code="required")
def filter_queryset(self, attrs, queryset, serializer):
"""
Filter the queryset to all instances matching the given attributes.
"""
# field names => field sources
sources = [serializer.fields[field_name].source for field_name in self.fields]
# If this is an update, then any unprovided field should
# have it's value set based on the existing instance attribute.
if serializer.instance is not None:
for source in sources:
if source not in attrs:
attrs[source.replace(".", "__")] = nested_getattr(
serializer.instance, source
)
# Determine the filter keyword arguments and filter the queryset.
filter_kwargs = {
source.replace(".", "__"): nested_get(attrs, source) for source in sources
}
return qs_filter(queryset, **filter_kwargs)
@staticmethod
def exclude_current_instance(queryset, instance):
"""
If an instance is being updated, then do not include
that instance itself as a uniqueness conflict.
"""
if instance is not None:
return queryset.exclude(pk=instance.pk)
return queryset
def __call__(self, attrs, serializer):
# self.enforce_required_fields(attrs, serializer)
queryset = self.queryset
queryset = self.filter_queryset(attrs, queryset, serializer)
queryset = self.exclude_current_instance(queryset, serializer.instance)
# Ignore validation if any field is None
checked_values = [
value for field, value in attrs.items() if field in self.fields
]
logger.debug(f"{checked_values=}")
if None not in checked_values and qs_exists(queryset):
field_names = ", ".join(self.fields)
message = self.message.format(field_names=field_names)
raise ValidationError(message, code="unique")
def __repr__(self):
return "<%s(queryset=%s, fields=%s)>" % (
self.__class__.__name__,
smart_repr(self.queryset),
smart_repr(self.fields),
)
在我的序列化程序中,我通过 Meta 类使用它,就像在原始问题中一样。