-
-
Notifications
You must be signed in to change notification settings - Fork 7k
fix(blank/unique): add default handling for blank=True field
#9751
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?
fix(blank/unique): add default handling for blank=True field
#9751
Conversation
Signed-off-by: Sergei Shishov <[email protected]>
3bcd191 to
8de6a6c
Compare
|
I have added tests similar to the tests added in #9531 |
auvipy
left a comment
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.
did you check this pr #9725 ?
| class Meta: | ||
| constraints = [ | ||
| # Unique constraint on 2 blank fields | ||
| models.UniqueConstraint(name='unique_constraint', fields=('age', 'tag'), condition=~models.Q(models.Q(title='') & models.Q(tag='True'))) |
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.
Don't we have a char length limit here?
|
|
||
| def test_blank_uqnique_constraint_fields_are_not_required(self): | ||
| serializer = UniqueConstraintBlankSerializer(data={'age': 25}) | ||
| self.assertTrue(serializer.is_valid(), serializer.errors) |
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.
Can we split it into multiple assertions?
| ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators if hasattr(v, "queryset")} | ||
| assert ids_in_qs == {frozenset([1]), frozenset([3])} | ||
|
|
||
| def test_blank_uqnique_constraint_fields_are_not_required(self): |
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.
Typo here
|
Hi @sshishov @auvipy @deepakangadi, I can help unblock this by:
If this sounds good, I can start working on these changes. Should I update this branch (if permitted) or create a new PR? Thanks for your guidance! |
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
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.
Pull request overview
This PR fixes an issue where blank=True fields in unique_together constraints were incorrectly becoming required. The fix adds special handling for blank fields by setting their default value to an empty string, similar to how null=True fields default to None. This ensures that blank fields in uniqueness constraints remain optional while still properly validating uniqueness.
Key Changes
- Added
default=''handling for blank fields in unique constraints inget_uniqueness_extra_kwargs - Added comprehensive test coverage for both
unique_togetherandUniqueConstraintwith blank fields - Tests verify that blank fields are not required, while still enforcing uniqueness validation
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| rest_framework/serializers.py | Added conditional logic to set default='' for blank fields in the uniqueness constraint handling, following the same pattern as null field handling |
| tests/test_validators.py | Added new test models (BlankUniquenessTogetherModel, UniqueConstraintBlankModel) and test cases to verify blank fields in uniqueness constraints are not required but still validate correctly |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| class BlankUniquenessTogetherModel(models.Model): | ||
| race_name = models.CharField(max_length=100, blank=True) | ||
| position = models.IntegerField() | ||
|
|
||
| class Meta: | ||
| unique_together = ('race_name', 'position') | ||
|
|
Copilot
AI
Dec 14, 2025
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.
Consider adding a docstring to BlankUniquenessTogetherModel similar to NullUniquenessTogetherModel (lines 160-172) to explain the purpose and expected behavior of blank fields in unique_together constraints.
|
|
||
| class Meta: | ||
| constraints = [ | ||
| # Unique constraint on 2 blank fields |
Copilot
AI
Dec 14, 2025
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.
The comment "Unique constraint on 2 blank fields" is misleading. The constraint is on 'age' and 'tag', but 'age' is an IntegerField without blank=True. Only 'tag' has blank=True. Consider updating the comment to accurately reflect that the constraint includes one required field (age) and one blank field (tag).
| # Unique constraint on 2 blank fields | |
| # Unique constraint on one required field (age) and one blank field (tag) |
| default = '' | ||
| else: | ||
| default = empty |
Copilot
AI
Dec 14, 2025
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.
Consider adding a field type check before setting default to empty string. The Django blank attribute is primarily meaningful for text fields (CharField, TextField). For other field types with blank=True, setting default='' could cause type errors. While this is an edge case (as non-text fields with blank=True typically also have null=True or an explicit default, which are handled earlier in the condition chain), adding a check like isinstance(unique_constraint_field, (models.CharField, models.TextField)) would make the code more defensive and consistent with the pattern in utils/field_mapping.py lines 133-134.
| default = '' | |
| else: | |
| default = empty | |
| if isinstance(unique_constraint_field, (models.CharField, models.TextField)): | |
| default = '' | |
| else: | |
| default = empty |
| ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators if hasattr(v, "queryset")} | ||
| assert ids_in_qs == {frozenset([1]), frozenset([3])} | ||
|
|
||
| def test_blank_uqnique_constraint_fields_are_not_required(self): |
Copilot
AI
Dec 14, 2025
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.
Typo in test method name: "uqnique" should be "unique".
| def test_blank_uqnique_constraint_fields_are_not_required(self): | |
| def test_blank_unique_constraint_fields_are_not_required(self): |
| def test_validation_for_missing_blank_fields(self): | ||
| BlankUniquenessTogetherModel.objects.create( | ||
| position=1 | ||
| ) | ||
| data = { | ||
| 'position': 1 | ||
| } | ||
| serializer = BlankUniquenessTogetherSerializer(data=data) | ||
| assert not serializer.is_valid() | ||
|
|
||
| def test_ignore_validation_for_missing_blank_fields(self): | ||
| data = { | ||
| 'position': 1 | ||
| } | ||
| serializer = BlankUniquenessTogetherSerializer(data=data) | ||
| assert serializer.is_valid(), serializer.errors | ||
|
|
Copilot
AI
Dec 14, 2025
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.
The test names test_validation_for_missing_blank_fields and test_ignore_validation_for_missing_blank_fields are confusing because both refer to "missing blank fields" but have opposite expectations. Consider renaming to make the distinction clearer, such as test_validation_fails_for_duplicate_blank_defaults and test_validation_passes_when_no_duplicates.
auvipy
left a comment
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.
please also fix the merge conflicts
Description
This MR should fix the issue with
blank=Truefield inUniqueTogetherconstraint. It should not become required all of a sudden.Fixes #9750