Refs #18682 -- Edited explanation in stale content type deletion.

Follow up to 8db889eaf7.
This commit is contained in:
Tim Graham 2016-07-15 11:07:43 -04:00
parent 7399fee6c3
commit e2dfa81ff7
4 changed files with 23 additions and 20 deletions

View File

@ -154,22 +154,20 @@ def update_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT
for obj_type, objs in collector.data.items(): for obj_type, objs in collector.data.items():
if objs == {ct}: if objs == {ct}:
continue continue
ct_info.append(' - %s object%s of type %s.%s:' % ( ct_info.append(' - %s %s object(s)' % (
len(objs), len(objs),
's' if len(objs) != 1 else '', obj_type._meta.label,
obj_type._meta.app_label, ))
obj_type._meta.model_name)
)
content_type_display = '\n'.join(ct_info) content_type_display = '\n'.join(ct_info)
print("""Some content types in your database are stale and can be deleted. print("""Some content types in your database are stale and can be deleted.
Any objects that depend on these content types will then also be deleted. Any objects that depend on these content types will also be deleted.
The content types, and the dependent objects that would be deleted, are: The content types and dependent objects that would be deleted are:
%s %s
This list does not include data that might be in your database This list doesn't include any cascade deletions to data outside of Django's
outside of Django's models. models (uncommon).
Are you sure you want to delete these content types? Are you sure you want to delete these content types?
If you're unsure, answer 'no'. If you're unsure, answer 'no'.
@ -191,7 +189,6 @@ If you're unsure, answer 'no'.
class NoFastDeleteCollector(Collector): class NoFastDeleteCollector(Collector):
def can_fast_delete(self, *args, **kwargs): def can_fast_delete(self, *args, **kwargs):
""" """
We always want to load the objects into memory so that we can display Always load related objects to display them when showing confirmation.
them to the user when asking confirmation.
""" """
return False return False

View File

@ -87,10 +87,9 @@ Minor features
:mod:`django.contrib.contenttypes` :mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* When stale content types are detected during a management command, there is * When stale content types are detected after the ``migrate`` command, there's
now an expansive list of objects that will be deleted. Previously, only now a list of related objects such as ``auth.Permission``\s that will also be
the content type objects themselves were listed, even if there were objects deleted. Previously, only the content types were listed.
with foreign keys towards the content types that would be deleted also.
:mod:`django.contrib.gis` :mod:`django.contrib.gis`
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -131,6 +131,7 @@ class Post(models.Model):
class ModelWithNullFKToSite(models.Model): class ModelWithNullFKToSite(models.Model):
title = models.CharField(max_length=200) title = models.CharField(max_length=200)
site = models.ForeignKey(Site, null=True, on_delete=models.CASCADE) site = models.ForeignKey(Site, null=True, on_delete=models.CASCADE)
post = models.ForeignKey(Post, null=True, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return self.title return self.title

View File

@ -389,21 +389,27 @@ class UpdateContentTypesTests(TestCase):
def test_interactive_true_with_dependent_objects(self): def test_interactive_true_with_dependent_objects(self):
""" """
interactive mode of update_contenttypes() (the default) should delete interactive mode of update_contenttypes() (the default) should delete
stale contenttypes and warn of dependent objects stale contenttypes and warn of dependent objects.
""" """
Post.objects.create(title='post', content_type=self.content_type) post = Post.objects.create(title='post', content_type=self.content_type)
# A related object is needed to show that a custom collector with
# can_fast_delete=False is needed.
ModelWithNullFKToSite.objects.create(post=post)
contenttypes_management.input = lambda x: force_str("yes") contenttypes_management.input = lambda x: force_str("yes")
with captured_stdout() as stdout: with captured_stdout() as stdout:
contenttypes_management.update_contenttypes(self.app_config) contenttypes_management.update_contenttypes(self.app_config)
self.assertEqual(Post.objects.count(), 0) self.assertEqual(Post.objects.count(), 0)
self.assertIn("1 object of type contenttypes_tests.post:", stdout.getvalue()) output = stdout.getvalue()
self.assertIn("Deleting stale content type", stdout.getvalue()) self.assertIn('- Content type for contenttypes_tests.Fake', output)
self.assertIn('- 1 contenttypes_tests.Post object(s)', output)
self.assertIn('- 1 contenttypes_tests.ModelWithNullFKToSite', output)
self.assertIn('Deleting stale content type', output)
self.assertEqual(ContentType.objects.count(), self.before_count) self.assertEqual(ContentType.objects.count(), self.before_count)
def test_interactive_true_without_dependent_objects(self): def test_interactive_true_without_dependent_objects(self):
""" """
interactive mode of update_contenttypes() (the default) should delete interactive mode of update_contenttypes() (the default) should delete
stale contenttypes and inform there are no dependent objects stale contenttypes even if there aren't any dependent objects.
""" """
contenttypes_management.input = lambda x: force_str("yes") contenttypes_management.input = lambda x: force_str("yes")
with captured_stdout() as stdout: with captured_stdout() as stdout: