mysteriendrama/lib/python3.11/site-packages/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py
2023-07-22 12:13:39 +02:00

111 lines
4.4 KiB
Python

import itertools
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.core.management import BaseCommand
from django.db import DEFAULT_DB_ALIAS, router
from django.db.models.deletion import Collector
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--noinput",
"--no-input",
action="store_false",
dest="interactive",
help="Tells Django to NOT prompt the user for input of any kind.",
)
parser.add_argument(
"--database",
default=DEFAULT_DB_ALIAS,
help='Nominates the database to use. Defaults to the "default" database.',
)
parser.add_argument(
"--include-stale-apps",
action="store_true",
default=False,
help=(
"Deletes stale content types including ones from previously "
"installed apps that have been removed from INSTALLED_APPS."
),
)
def handle(self, **options):
db = options["database"]
include_stale_apps = options["include_stale_apps"]
interactive = options["interactive"]
verbosity = options["verbosity"]
if not router.allow_migrate_model(db, ContentType):
return
ContentType.objects.clear_cache()
apps_content_types = itertools.groupby(
ContentType.objects.using(db).order_by("app_label", "model"),
lambda obj: obj.app_label,
)
for app_label, content_types in apps_content_types:
if not include_stale_apps and app_label not in apps.app_configs:
continue
to_remove = [ct for ct in content_types if ct.model_class() is None]
# Confirm that the content type is stale before deletion.
using = router.db_for_write(ContentType)
if to_remove:
if interactive:
ct_info = []
for ct in to_remove:
ct_info.append(
" - Content type for %s.%s" % (ct.app_label, ct.model)
)
collector = NoFastDeleteCollector(using=using, origin=ct)
collector.collect([ct])
for obj_type, objs in collector.data.items():
if objs != {ct}:
ct_info.append(
" - %s %s object(s)"
% (
len(objs),
obj_type._meta.label,
)
)
content_type_display = "\n".join(ct_info)
self.stdout.write(
"Some content types in your database are stale and can be "
"deleted.\n"
"Any objects that depend on these content types will also be "
"deleted.\n"
"The content types and dependent objects that would be deleted "
"are:\n\n"
f"{content_type_display}\n\n"
"This list doesn't include any cascade deletions to data "
"outside of Django's\n"
"models (uncommon).\n\n"
"Are you sure you want to delete these content types?\n"
"If you're unsure, answer 'no'."
)
ok_to_delete = input("Type 'yes' to continue, or 'no' to cancel: ")
else:
ok_to_delete = "yes"
if ok_to_delete == "yes":
for ct in to_remove:
if verbosity >= 2:
self.stdout.write(
"Deleting stale content type '%s | %s'"
% (ct.app_label, ct.model)
)
ct.delete()
else:
if verbosity >= 2:
self.stdout.write("Stale content types remain.")
class NoFastDeleteCollector(Collector):
def can_fast_delete(self, *args, **kwargs):
"""
Always load related objects to display them when showing confirmation.
"""
return False