Skip to content

Commit

Permalink
Do not exit early when validating partial unique indexes
Browse files Browse the repository at this point in the history
Instead, evaluate the validation of every unique index.
  • Loading branch information
Peter Bex committed Jul 22, 2019
1 parent 9599c91 commit 196fb22
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 3 deletions.
13 changes: 11 additions & 2 deletions partial_index/mixins.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import defaultdict
from django.core.exceptions import ImproperlyConfigured, ValidationError, NON_FIELD_ERRORS
from django.db.models import Q

Expand Down Expand Up @@ -73,6 +74,7 @@ def validate_partial_unique(self):
if unique_idxs:
model_fields = set(f.name for f in self._meta.get_fields(include_parents=True, include_hidden=True))

errors = defaultdict(list)
for idx in unique_idxs:
where = idx.where
if not isinstance(where, Q):
Expand All @@ -89,16 +91,20 @@ def validate_partial_unique(self):
'This is a bug in the PartialIndex definition or the django-partial-index library itself.')

values = {}
skip = False
for field_name in mentioned_fields:
field_value = getattr(self, field_name)
if field_value is None and field_name in idx.fields:
# Can never be unique if value is NULL. If
# field is non-nullable we'll get a validation
# error from the field validations themselves.
return
skip = True
else:
values[field_name] = field_value

if skip:
continue

conflict = self.__class__.objects.filter(**values) # Step 1 and 3
conflict = conflict.filter(where) # Step 2
if self.pk:
Expand All @@ -109,4 +115,7 @@ def validate_partial_unique(self):
key = idx.fields[0]
else:
key = NON_FIELD_ERRORS
raise PartialUniqueValidationError({key: self.unique_error_message(self.__class__, sorted(idx.fields))})
errors[key].append(self.unique_error_message(self.__class__, sorted(idx.fields)))

if errors:
raise PartialUniqueValidationError(errors)
19 changes: 18 additions & 1 deletion tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def test_nullable_roomnumber_q_same_no_conflict_for_null_number(self):
NullableRoomNumberQ.objects.create(room=self.room1, room_number=None)


class PartialIndexLabelValidationTest(TestCase):
class PartialIndexLabelValidationTest(TransactionTestCase):
"""Test that partial unique validations are all executed."""

def setUp(self):
Expand Down Expand Up @@ -267,3 +267,20 @@ def test_standard_unique_together_constraints_do_not_block_evaluation_of_partial

with self.assertRaises(IntegrityError):
label.save()

def test_all_partial_constraints_are_included_in_validation_errors(self):
Label.objects.create(label='a', user=self.user1, room=self.room1, uuid='11111111-0000-0000-0000-000000000000', created_at='2019-01-01T11:11:11')

label = Label(label='a', user=self.user1, room=self.room1, uuid='22222222-0000-0000-0000-000000000000', created_at='2019-01-02T22:22:22')
with self.assertRaises(ValidationError) as cm:
label.full_clean()

self.assertSetEqual({NON_FIELD_ERRORS}, set(cm.exception.message_dict.keys()))
self.assertEqual(2, len(cm.exception.error_dict[NON_FIELD_ERRORS]))
self.assertEqual('unique_together', cm.exception.error_dict[NON_FIELD_ERRORS][0].code)
self.assertEqual(['label', 'room'], cm.exception.error_dict[NON_FIELD_ERRORS][0].params['unique_check'])
self.assertEqual('unique_together', cm.exception.error_dict[NON_FIELD_ERRORS][1].code)
self.assertEqual(['label', 'user'], cm.exception.error_dict[NON_FIELD_ERRORS][1].params['unique_check'])

with self.assertRaises(IntegrityError):
label.save()

0 comments on commit 196fb22

Please sign in to comment.