Fixed #25320 -- Reverted ManyToManyField.null to False for backwards compatibility.

Thanks Tom Christie for the report and review.
This commit is contained in:
Tim Graham 2015-08-28 09:25:54 -04:00
parent 4c30fa905d
commit 064d4b1cb0
4 changed files with 26 additions and 5 deletions

View File

@ -176,8 +176,16 @@ class RelatedFieldListFilter(FieldListFilter):
self.title = self.lookup_title self.title = self.lookup_title
self.empty_value_display = model_admin.get_empty_value_display() self.empty_value_display = model_admin.get_empty_value_display()
@property
def include_empty_choice(self):
"""
Return True if a "(None)" choice should be included, which filters
out everything except empty relationships.
"""
return self.field.null or (self.field.is_relation and self.field.many_to_many)
def has_output(self): def has_output(self):
if self.field.null: if self.include_empty_choice:
extra = 1 extra = 1
else: else:
extra = 0 extra = 0
@ -204,7 +212,7 @@ class RelatedFieldListFilter(FieldListFilter):
}, [self.lookup_kwarg_isnull]), }, [self.lookup_kwarg_isnull]),
'display': val, 'display': val,
} }
if self.field.null: if self.include_empty_choice:
yield { yield {
'selected': bool(self.lookup_val_isnull), 'selected': bool(self.lookup_val_isnull),
'query_string': cl.get_query_string({ 'query_string': cl.get_query_string({

View File

@ -2304,8 +2304,6 @@ class ManyToManyField(RelatedField):
self.db_table = db_table self.db_table = db_table
self.swappable = swappable self.swappable = swappable
# Many-to-many fields are always nullable.
self.null = True
def check(self, **kwargs): def check(self, **kwargs):
errors = super(ManyToManyField, self).check(**kwargs) errors = super(ManyToManyField, self).check(**kwargs)
@ -2552,7 +2550,6 @@ class ManyToManyField(RelatedField):
def deconstruct(self): def deconstruct(self):
name, path, args, kwargs = super(ManyToManyField, self).deconstruct() name, path, args, kwargs = super(ManyToManyField, self).deconstruct()
# Handle the simpler arguments. # Handle the simpler arguments.
del kwargs["null"]
if self.db_table is not None: if self.db_table is not None:
kwargs['db_table'] = self.db_table kwargs['db_table'] = self.db_table
if self.remote_field.db_constraint is not True: if self.remote_field.db_constraint is not True:

View File

@ -523,6 +523,16 @@ class ListFiltersTests(TestCase):
self.assertEqual(choice['selected'], True) self.assertEqual(choice['selected'], True)
self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk) self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk)
# With one book, the list filter should appear because there is also a
# (None) option.
Book.objects.exclude(pk=self.djangonaut_book.pk).delete()
filterspec = changelist.get_filters(request)[0]
self.assertEqual(len(filterspec), 2)
# With no books remaining, no list filters should appear.
Book.objects.all().delete()
filterspec = changelist.get_filters(request)[0]
self.assertEqual(len(filterspec), 0)
def test_relatedonlyfieldlistfilter_foreignkey(self): def test_relatedonlyfieldlistfilter_foreignkey(self):
modeladmin = BookAdminRelatedOnlyFilter(Book, site) modeladmin = BookAdminRelatedOnlyFilter(Book, site)

View File

@ -216,3 +216,9 @@ class FieldFlagsTests(test.SimpleTestCase):
reverse_field = field.remote_field reverse_field = field.remote_field
self.assertEqual(field.model, reverse_field.related_model) self.assertEqual(field.model, reverse_field.related_model)
self.assertEqual(field.related_model, reverse_field.model) self.assertEqual(field.related_model, reverse_field.model)
def test_null(self):
# null isn't well defined for a ManyToManyField, but changing it to
# True causes backwards compatibility problems (#25320).
self.assertFalse(AllFieldsModel._meta.get_field('m2m').null)
self.assertTrue(AllFieldsModel._meta.get_field('reverse2').null)