-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Support violation_error_code and violation_error_message from UniqueConstraint in UniqueTogetherValidator #9766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Support violation_error_code and violation_error_message from UniqueConstraint in UniqueTogetherValidator #9766
Conversation
rest_framework/serializers.py
Outdated
""" | ||
for parent_class in [model] + list(model._meta.parents): | ||
for unique_together in parent_class._meta.unique_together: | ||
yield unique_together, model._default_manager, [], None | ||
yield unique_together, model._default_manager, [], None, None, None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change: Callers consuming the iterator will experience ValueError: too many values to unpack (expected 4)
.
For backwards compatibility, the old iterator structure should be returned, unless the new functionality is requested (such as via a default-False
argument).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review. I agree it was a critical change — I’ve restored the original signature and took a different approach.
What's the use case for that? (Why not use model-level default?) |
…ture Extracted error message logic to a separate method. fix: conditionally include violation_error_code for Django >= 5.0 fix(validators): use custom error message and code from model constraints
24e69af
to
c54c658
Compare
Returning the constraint’s default message would change the output and might break users relying on the validator’s default message—for example, in tests or UI rendering. I consider that the current behaviour should be maintained. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall, just minor questions!
rest_framework/serializers.py
Outdated
@@ -1595,6 +1606,11 @@ def get_unique_together_validators(self): | |||
for name, source in field_sources.items(): | |||
source_map[source].append(name) | |||
|
|||
unique_constraint_by_fields = { | |||
constraint.fields: constraint for constraint in self.Meta.model._meta.constraints |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop is over self.Meta.model._meta.constraints
while the loop in get_unique_together_constraints()
is over [model] + list(model._meta.parents)
. Is this difference intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, thanks.
To be honest, I have doubts about the necessity of collecting constraints from parent models, as this contradicts the expected behavior of the framework.
-
When inheriting from an abstract model, the Meta class is fully inherited, including constraints. And we have a possibility to overload it. Django handles this correctly, and no additional parent traversal is required.
-
When inheriting from a non-abstract model (multi-table inheritance), a separate table is created with its own Meta class and set of constraints (if specified). The parent model’s constraints apply only to the parent model and should not be considered during validation of the child model.
Thus, collecting constraints from parent models is not only redundant, but may also lead to incorrect logic.
There’s also an open question: if we are traversing parents at all, why only direct parents (_meta.parents) and not the full inheritance tree (_meta.all_parents)? This seems inconsistent.
For now, I’m preserving the current (get_unique_together_constraints
-like) behavior to avoid unnecessary deviation, but I believe this deserves further discussion and potential improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with this assessment, both that the current traversal approach may need revision, but also that this PR should continue to adhere to the current approach. @browniebroke FYI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm! Let's wait for @browniebroke's comment on the open conversation and then we're done!
Added support for retrieving custom violation_error_code (for django 5+) and violation_error_message from Django model-level UniqueConstraint definitions and passing them to UniqueTogetherValidator.
refs #9714 and #9352
The update ensures that:
• If violation_error_message or violation_error_code are explicitly defined on the model constraint, they are forwarded to the corresponding validator.
• If the constraint uses the default message/code, they are not passed, allowing the validator to use its own defaults.
Additionally, tests have been added to cover both scenarios:
• When a custom error message/code is used.
• When defaults are applied.
Note: The current structure of
get_unique_together_constraints()
may benefit from refactoring (e.g., returning a named structure for clarity and extensibility). This is outside the scope of this PR, but I may explore it in a future contribution.