Fixed #31187 -- Fixed detecting of existing total ordering in admin changelist when using Meta.constraints.

Detection of existing total ordering in admin changelist now takes into
account non-partial unique constraints.
This commit is contained in:
Fabio Sangiovanni 2020-01-20 20:50:30 +01:00 committed by Mariusz Felisiak
parent d270c10a72
commit b457068cf2
2 changed files with 102 additions and 2 deletions

View File

@ -367,8 +367,16 @@ class ChangeList:
break
ordering_fields.add(field.attname)
else:
# No single total ordering field, try unique_together.
for field_names in self.lookup_opts.unique_together:
# No single total ordering field, try unique_together and total
# unique constraints.
constraint_field_names = (
*self.lookup_opts.unique_together,
*(
constraint.fields
for constraint in self.lookup_opts.total_unique_constraints
),
)
for field_names in constraint_field_names:
# Normalize attname references by using get_field().
fields = [self.lookup_opts.get_field(field_name) for field_name in field_names]
# Composite unique constraints containing a nullable column

View File

@ -1058,6 +1058,98 @@ class ChangeListTests(TestCase):
with self.subTest(ordering=ordering):
self.assertEqual(change_list._get_deterministic_ordering(ordering), expected)
@isolate_apps('admin_changelist')
def test_total_ordering_optimization_meta_constraints(self):
class Related(models.Model):
unique_field = models.BooleanField(unique=True)
class Meta:
ordering = ('unique_field',)
class Model(models.Model):
field_1 = models.BooleanField()
field_2 = models.BooleanField()
field_3 = models.BooleanField()
field_4 = models.BooleanField()
field_5 = models.BooleanField()
field_6 = models.BooleanField()
nullable_1 = models.BooleanField(null=True)
nullable_2 = models.BooleanField(null=True)
related_1 = models.ForeignKey(Related, models.CASCADE)
related_2 = models.ForeignKey(Related, models.CASCADE)
related_3 = models.ForeignKey(Related, models.CASCADE)
related_4 = models.ForeignKey(Related, models.CASCADE)
class Meta:
constraints = [
*[
models.UniqueConstraint(fields=fields, name=''.join(fields))
for fields in (
['field_1'],
['nullable_1'],
['related_1'],
['related_2_id'],
['field_2', 'field_3'],
['field_2', 'nullable_2'],
['field_2', 'related_3'],
['field_3', 'related_4_id'],
)
],
models.CheckConstraint(check=models.Q(id__gt=0), name='foo'),
models.UniqueConstraint(
fields=['field_5'],
condition=models.Q(id__gt=10),
name='total_ordering_1',
),
models.UniqueConstraint(
fields=['field_6'],
condition=models.Q(),
name='total_ordering',
),
]
class ModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return Model.objects.none()
request = self._mocked_authenticated_request('/', self.superuser)
site = admin.AdminSite(name='admin')
model_admin = ModelAdmin(Model, site)
change_list = model_admin.get_changelist_instance(request)
tests = (
# Unique non-nullable field.
(['field_1'], ['field_1']),
# Unique nullable field.
(['nullable_1'], ['nullable_1', '-pk']),
# Related attname unique.
(['related_1_id'], ['related_1_id']),
(['related_2_id'], ['related_2_id']),
# Related ordering introspection is not implemented.
(['related_1'], ['related_1', '-pk']),
# Composite unique.
(['-field_2', 'field_3'], ['-field_2', 'field_3']),
# Composite unique nullable.
(['field_2', '-nullable_2'], ['field_2', '-nullable_2', '-pk']),
# Composite unique and nullable.
(
['field_2', '-nullable_2', 'field_3'],
['field_2', '-nullable_2', 'field_3'],
),
# Composite field and related field name.
(['field_2', '-related_3'], ['field_2', '-related_3', '-pk']),
(['field_3', 'related_4'], ['field_3', 'related_4', '-pk']),
# Composite field and related field attname.
(['field_2', 'related_3_id'], ['field_2', 'related_3_id']),
(['field_3', '-related_4_id'], ['field_3', '-related_4_id']),
# Partial unique constraint is ignored.
(['field_5'], ['field_5', '-pk']),
# Unique constraint with an empty condition.
(['field_6'], ['field_6']),
)
for ordering, expected in tests:
with self.subTest(ordering=ordering):
self.assertEqual(change_list._get_deterministic_ordering(ordering), expected)
def test_dynamic_list_filter(self):
"""
Regression tests for ticket #17646: dynamic list_filter support.