mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-09-16 09:12:29 +03:00
Merge 58e856527c
into 0f576223f2
This commit is contained in:
commit
767d870d85
|
@ -1569,6 +1569,17 @@ class ModelSerializer(Serializer):
|
||||||
self.get_unique_for_date_validators()
|
self.get_unique_for_date_validators()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_constraint_violation_error_message(self, constraint):
|
||||||
|
"""
|
||||||
|
Returns the violation error message for the UniqueConstraint,
|
||||||
|
or None if the message is the default.
|
||||||
|
"""
|
||||||
|
violation_error_message = constraint.get_violation_error_message()
|
||||||
|
default_error_message = constraint.default_violation_error_message % {"name": constraint.name}
|
||||||
|
if violation_error_message == default_error_message:
|
||||||
|
return None
|
||||||
|
return violation_error_message
|
||||||
|
|
||||||
def get_unique_together_validators(self):
|
def get_unique_together_validators(self):
|
||||||
"""
|
"""
|
||||||
Determine a default set of validators for any unique_together constraints.
|
Determine a default set of validators for any unique_together constraints.
|
||||||
|
@ -1595,6 +1606,13 @@ class ModelSerializer(Serializer):
|
||||||
for name, source in field_sources.items():
|
for name, source in field_sources.items():
|
||||||
source_map[source].append(name)
|
source_map[source].append(name)
|
||||||
|
|
||||||
|
unique_constraint_by_fields = {
|
||||||
|
constraint.fields: constraint
|
||||||
|
for model_cls in (self.Meta.model, *self.Meta.model._meta.parents)
|
||||||
|
for constraint in model_cls._meta.constraints
|
||||||
|
if isinstance(constraint, models.UniqueConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
# Note that we make sure to check `unique_together` both on the
|
# Note that we make sure to check `unique_together` both on the
|
||||||
# base model class, but also on any parent classes.
|
# base model class, but also on any parent classes.
|
||||||
validators = []
|
validators = []
|
||||||
|
@ -1621,11 +1639,17 @@ class ModelSerializer(Serializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
field_names = tuple(source_map[f][0] for f in unique_together)
|
field_names = tuple(source_map[f][0] for f in unique_together)
|
||||||
|
|
||||||
|
constraint = unique_constraint_by_fields.get(tuple(unique_together))
|
||||||
|
violation_error_message = self._get_constraint_violation_error_message(constraint) if constraint else None
|
||||||
|
|
||||||
validator = UniqueTogetherValidator(
|
validator = UniqueTogetherValidator(
|
||||||
queryset=queryset,
|
queryset=queryset,
|
||||||
fields=field_names,
|
fields=field_names,
|
||||||
condition_fields=tuple(source_map[f][0] for f in condition_fields),
|
condition_fields=tuple(source_map[f][0] for f in condition_fields),
|
||||||
condition=condition,
|
condition=condition,
|
||||||
|
message=violation_error_message,
|
||||||
|
code=getattr(constraint, 'violation_error_code', None),
|
||||||
)
|
)
|
||||||
validators.append(validator)
|
validators.append(validator)
|
||||||
return validators
|
return validators
|
||||||
|
|
|
@ -111,13 +111,15 @@ class UniqueTogetherValidator:
|
||||||
message = _('The fields {field_names} must make a unique set.')
|
message = _('The fields {field_names} must make a unique set.')
|
||||||
missing_message = _('This field is required.')
|
missing_message = _('This field is required.')
|
||||||
requires_context = True
|
requires_context = True
|
||||||
|
code = 'unique'
|
||||||
|
|
||||||
def __init__(self, queryset, fields, message=None, condition_fields=None, condition=None):
|
def __init__(self, queryset, fields, message=None, condition_fields=None, condition=None, code=None):
|
||||||
self.queryset = queryset
|
self.queryset = queryset
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
self.message = message or self.message
|
self.message = message or self.message
|
||||||
self.condition_fields = [] if condition_fields is None else condition_fields
|
self.condition_fields = [] if condition_fields is None else condition_fields
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
|
self.code = code or self.code
|
||||||
|
|
||||||
def enforce_required_fields(self, attrs, serializer):
|
def enforce_required_fields(self, attrs, serializer):
|
||||||
"""
|
"""
|
||||||
|
@ -198,7 +200,7 @@ class UniqueTogetherValidator:
|
||||||
if checked_values and None not in checked_values and qs_exists_with_condition(queryset, self.condition, condition_kwargs):
|
if checked_values and None not in checked_values and qs_exists_with_condition(queryset, self.condition, condition_kwargs):
|
||||||
field_names = ', '.join(self.fields)
|
field_names = ', '.join(self.fields)
|
||||||
message = self.message.format(field_names=field_names)
|
message = self.message.format(field_names=field_names)
|
||||||
raise ValidationError(message, code='unique')
|
raise ValidationError(message, code=self.code)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{}({})>'.format(
|
return '<{}({})>'.format(
|
||||||
|
@ -217,6 +219,7 @@ class UniqueTogetherValidator:
|
||||||
and self.missing_message == other.missing_message
|
and self.missing_message == other.missing_message
|
||||||
and self.queryset == other.queryset
|
and self.queryset == other.queryset
|
||||||
and self.fields == other.fields
|
and self.fields == other.fields
|
||||||
|
and self.code == other.code
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -616,6 +616,26 @@ class UniqueConstraintNullableModel(models.Model):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UniqueConstraintCustomMessageCodeModel(models.Model):
|
||||||
|
username = models.CharField(max_length=32)
|
||||||
|
company_id = models.IntegerField()
|
||||||
|
role = models.CharField(max_length=32)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=("username", "company_id"),
|
||||||
|
name="unique_username_company_custom_msg",
|
||||||
|
violation_error_message="Username must be unique within a company.",
|
||||||
|
**(dict(violation_error_code="duplicate_username") if django_version[0] >= 5 else {}),
|
||||||
|
),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=("company_id", "role"),
|
||||||
|
name="unique_company_role_default_msg",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class UniqueConstraintSerializer(serializers.ModelSerializer):
|
class UniqueConstraintSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UniqueConstraintModel
|
model = UniqueConstraintModel
|
||||||
|
@ -628,6 +648,12 @@ class UniqueConstraintNullableSerializer(serializers.ModelSerializer):
|
||||||
fields = ('title', 'age', 'tag')
|
fields = ('title', 'age', 'tag')
|
||||||
|
|
||||||
|
|
||||||
|
class UniqueConstraintCustomMessageCodeSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = UniqueConstraintCustomMessageCodeModel
|
||||||
|
fields = ('username', 'company_id', 'role')
|
||||||
|
|
||||||
|
|
||||||
class TestUniqueConstraintValidation(TestCase):
|
class TestUniqueConstraintValidation(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.instance = UniqueConstraintModel.objects.create(
|
self.instance = UniqueConstraintModel.objects.create(
|
||||||
|
@ -778,6 +804,31 @@ class TestUniqueConstraintValidation(TestCase):
|
||||||
)
|
)
|
||||||
assert serializer.is_valid()
|
assert serializer.is_valid()
|
||||||
|
|
||||||
|
def test_unique_constraint_custom_message_code(self):
|
||||||
|
UniqueConstraintCustomMessageCodeModel.objects.create(username="Alice", company_id=1, role="member")
|
||||||
|
expected_code = "duplicate_username" if django_version[0] >= 5 else UniqueTogetherValidator.code
|
||||||
|
|
||||||
|
serializer = UniqueConstraintCustomMessageCodeSerializer(data={
|
||||||
|
"username": "Alice",
|
||||||
|
"company_id": 1,
|
||||||
|
"role": "admin",
|
||||||
|
})
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert serializer.errors == {"non_field_errors": ["Username must be unique within a company."]}
|
||||||
|
assert serializer.errors["non_field_errors"][0].code == expected_code
|
||||||
|
|
||||||
|
def test_unique_constraint_default_message_code(self):
|
||||||
|
UniqueConstraintCustomMessageCodeModel.objects.create(username="Alice", company_id=1, role="member")
|
||||||
|
serializer = UniqueConstraintCustomMessageCodeSerializer(data={
|
||||||
|
"username": "John",
|
||||||
|
"company_id": 1,
|
||||||
|
"role": "member",
|
||||||
|
})
|
||||||
|
expected_message = UniqueTogetherValidator.message.format(field_names=', '.join(("company_id", "role")))
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert serializer.errors == {"non_field_errors": [expected_message]}
|
||||||
|
assert serializer.errors["non_field_errors"][0].code == UniqueTogetherValidator.code
|
||||||
|
|
||||||
|
|
||||||
# Tests for `UniqueForDateValidator`
|
# Tests for `UniqueForDateValidator`
|
||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
|
|
Loading…
Reference in New Issue
Block a user