dynamicweb/cms/management/commands/subcommands/delete_orphaned_plugins.py

121 lines
5 KiB
Python

from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models import Exists, OuterRef
from cms.management.commands.subcommands.list import plugin_report
from cms.models.placeholdermodel import Placeholder
from cms.models.pluginmodel import CMSPlugin
from .base import SubcommandsCommand
class DeleteOrphanedPluginsCommand(SubcommandsCommand):
help_string = (
'Delete plugins from the CMSPlugins table that should have instances '
'but don\'t, and ones for which a corresponding plugin model can no '
'longer be found. ATTENTION: Also removes plugins of apps currently not present in INSTALLED_APPS. '
'Ensure nothing is temporarily commented out.'
)
command_name = 'delete-orphaned-plugins'
def get_detached_placeholders(self):
detached_placeholder_ids = []
content_types = list(
Placeholder.objects.exclude(content_type_id__isnull=True)
.values_list('content_type_id', flat=True)
.distinct()
)
content_type_map = ContentType.objects.in_bulk(content_types)
for content_type_id in content_types:
content_type = content_type_map.get(content_type_id)
model_class = content_type.model_class() if content_type else None
if model_class is None:
detached_placeholder_ids.extend(
Placeholder.objects.filter(content_type_id=content_type_id)
.values_list('id', flat=True)
)
continue
detached_placeholder_ids.extend(
Placeholder.objects.filter(
content_type_id=content_type_id,
object_id__isnull=False,
)
.annotate(
object_exists=Exists(
model_class._base_manager.filter(pk=OuterRef('object_id'))
)
)
.filter(object_exists=False)
.values_list('id', flat=True)
)
return detached_placeholder_ids
def handle(self, *args, **options):
"""
Obtains a plugin report -
cms.management.commands.subcommands.list.plugin_report - and uses it
to delete orphaned plugins from the database, i.e. ones that are no
longer installed, and ones that have no corresponding saved plugin
instances (as will happen if a plugin is inserted into a placeholder,
but not saved).
"""
self.stdout.write('Obtaining plugin report\n')
uninstalled_instance_ids = []
unsaved_instance_ids = []
for plugin in plugin_report():
if not plugin['model']:
for instance in plugin['instances']:
uninstalled_instance_ids.append(instance.pk)
for instance in plugin['unsaved_instances']:
unsaved_instance_ids.append(instance.pk)
detached_placeholder_ids = self.get_detached_placeholders()
if options.get('interactive'):
confirm = input("""
You have requested to delete any instances of uninstalled plugins and empty plugin instances.
There are %d uninstalled plugins, %d empty plugins, and %d detached placeholders.
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: """ % (len(uninstalled_instance_ids), len(unsaved_instance_ids), len(detached_placeholder_ids)))
else:
confirm = 'yes'
if confirm == 'yes':
# delete items whose plugin is uninstalled and items with unsaved instances
self.stdout.write('... deleting any instances of uninstalled plugins, empty plugin instances, and detached placeholders\n')
with transaction.atomic():
if detached_placeholder_ids:
Placeholder.objects.filter(id__in=detached_placeholder_ids).delete()
for instance_id in uninstalled_instance_ids:
try:
instance = CMSPlugin.objects.get(pk=instance_id)
except CMSPlugin.DoesNotExist:
continue
if instance.placeholder:
instance.placeholder.delete_plugin(instance)
else:
instance.delete()
for instance_id in unsaved_instance_ids:
try:
instance = CMSPlugin.objects.get(pk=instance_id)
except CMSPlugin.DoesNotExist:
continue
if instance.placeholder:
instance.placeholder.delete_plugin(instance)
else:
instance.delete()
self.stdout.write(
f'Deleted instances of: \n {len(uninstalled_instance_ids)} uninstalled plugins \n {len(unsaved_instance_ids)} plugins with unsaved instances\n {len(detached_placeholder_ids)} detached placeholders\n'
)
self.stdout.write('all done\n')