This commit is contained in:
parent
f7e00b40c5
commit
8db889eaf7
|
@ -1,5 +1,6 @@
|
||||||
from django.apps import apps as global_apps
|
from django.apps import apps as global_apps
|
||||||
from django.db import DEFAULT_DB_ALIAS, migrations, router, transaction
|
from django.db import DEFAULT_DB_ALIAS, migrations, router, transaction
|
||||||
|
from django.db.models.deletion import Collector
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.six.moves import input
|
from django.utils.six.moves import input
|
||||||
|
@ -141,21 +142,39 @@ def update_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT
|
||||||
print("Adding content type '%s | %s'" % (ct.app_label, ct.model))
|
print("Adding content type '%s | %s'" % (ct.app_label, ct.model))
|
||||||
|
|
||||||
# Confirm that the content type is stale before deletion.
|
# Confirm that the content type is stale before deletion.
|
||||||
|
using = router.db_for_write(ContentType)
|
||||||
if to_remove:
|
if to_remove:
|
||||||
if interactive:
|
if interactive:
|
||||||
content_type_display = '\n'.join(
|
ct_info = []
|
||||||
' %s | %s' % (ct.app_label, ct.model)
|
for ct in to_remove:
|
||||||
for ct in to_remove
|
ct_info.append(' - Content type for %s.%s' % (ct.app_label, ct.model))
|
||||||
)
|
collector = NoFastDeleteCollector(using=using)
|
||||||
ok_to_delete = input("""The following content types are stale and need to be deleted:
|
collector.collect([ct])
|
||||||
|
|
||||||
|
for obj_type, objs in collector.data.items():
|
||||||
|
if objs == {ct}:
|
||||||
|
continue
|
||||||
|
ct_info.append(' - %s object%s of type %s.%s:' % (
|
||||||
|
len(objs),
|
||||||
|
's' if len(objs) != 1 else '',
|
||||||
|
obj_type._meta.app_label,
|
||||||
|
obj_type._meta.model_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
content_type_display = '\n'.join(ct_info)
|
||||||
|
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.
|
||||||
|
The content types, and the dependent objects that would be deleted, are:
|
||||||
|
|
||||||
%s
|
%s
|
||||||
|
|
||||||
Any objects related to these content types by a foreign key will also
|
This list does not include data that might be in your database
|
||||||
be deleted. Are you sure you want to delete these content types?
|
outside of Django's models.
|
||||||
If you're unsure, answer 'no'.
|
|
||||||
|
|
||||||
Type 'yes' to continue, or 'no' to cancel: """ % content_type_display)
|
Are you sure you want to delete these content types?
|
||||||
|
If you're unsure, answer 'no'.
|
||||||
|
""" % content_type_display)
|
||||||
|
ok_to_delete = input("Type 'yes' to continue, or 'no' to cancel: ")
|
||||||
else:
|
else:
|
||||||
ok_to_delete = False
|
ok_to_delete = False
|
||||||
|
|
||||||
|
@ -167,3 +186,12 @@ If you're unsure, answer 'no'.
|
||||||
else:
|
else:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print("Stale content types remain.")
|
print("Stale content types remain.")
|
||||||
|
|
||||||
|
|
||||||
|
class NoFastDeleteCollector(Collector):
|
||||||
|
def can_fast_delete(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
We always want to load the objects into memory so that we can display
|
||||||
|
them to the user when asking confirmation.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
|
@ -73,7 +73,10 @@ Minor features
|
||||||
:mod:`django.contrib.contenttypes`
|
:mod:`django.contrib.contenttypes`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* When stale content types are detected during a management command, there is
|
||||||
|
now an expansive list of objects that will be deleted. Previously, only
|
||||||
|
the content type objects themselves were listed, even if there were objects
|
||||||
|
with foreign keys towards the content types that would be deleted also.
|
||||||
|
|
||||||
:mod:`django.contrib.gis`
|
:mod:`django.contrib.gis`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -20,7 +20,7 @@ from django.test.utils import captured_stdout, isolate_apps
|
||||||
from django.utils.encoding import force_str, force_text
|
from django.utils.encoding import force_str, force_text
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Article, Author, ModelWithNullFKToSite, SchemeIncludedURL,
|
Article, Author, ModelWithNullFKToSite, Post, SchemeIncludedURL,
|
||||||
Site as MockSite,
|
Site as MockSite,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -383,13 +383,27 @@ class GenericRelationshipTests(SimpleTestCase):
|
||||||
class UpdateContentTypesTests(TestCase):
|
class UpdateContentTypesTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.before_count = ContentType.objects.count()
|
self.before_count = ContentType.objects.count()
|
||||||
ContentType.objects.create(app_label='contenttypes_tests', model='Fake')
|
self.content_type = ContentType.objects.create(app_label='contenttypes_tests', model='Fake')
|
||||||
self.app_config = apps.get_app_config('contenttypes_tests')
|
self.app_config = apps.get_app_config('contenttypes_tests')
|
||||||
|
|
||||||
def test_interactive_true(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.
|
stale contenttypes and warn of dependent objects
|
||||||
|
"""
|
||||||
|
Post.objects.create(title='post', content_type=self.content_type)
|
||||||
|
contenttypes_management.input = lambda x: force_str("yes")
|
||||||
|
with captured_stdout() as stdout:
|
||||||
|
contenttypes_management.update_contenttypes(self.app_config)
|
||||||
|
self.assertEqual(Post.objects.count(), 0)
|
||||||
|
self.assertIn("1 object of type contenttypes_tests.post:", stdout.getvalue())
|
||||||
|
self.assertIn("Deleting stale content type", stdout.getvalue())
|
||||||
|
self.assertEqual(ContentType.objects.count(), self.before_count)
|
||||||
|
|
||||||
|
def test_interactive_true_without_dependent_objects(self):
|
||||||
|
"""
|
||||||
|
interactive mode of update_contenttypes() (the default) should delete
|
||||||
|
stale contenttypes and inform there are no 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:
|
||||||
|
|
Loading…
Reference in New Issue