You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
84 lines
3.7 KiB
84 lines
3.7 KiB
import logging
|
|
from django.core.management.base import BaseCommand
|
|
from apps.bookmark.models import Bookmark
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Find and delete orphan bookmarks whose target content was deleted from the database."
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
'--dry-run',
|
|
action='store_true',
|
|
help='Scan and report orphan bookmarks without actually deleting them.',
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
dry_run = options.get('dry_run', False)
|
|
|
|
from apps.library.models import Book
|
|
from apps.podcast.models import Podcast, PodcastPlaylist
|
|
from apps.video.models import Video, VideoPlaylist
|
|
from apps.hadis.models import Hadis, HadisCorrection
|
|
from apps.article.models import Article
|
|
|
|
service_model_map = {
|
|
Bookmark.ServiceChoices.LIBRARY: (Book, 'Library Book'),
|
|
Bookmark.ServiceChoices.PODCAST: (Podcast, 'Podcast'),
|
|
Bookmark.ServiceChoices.PODCAST_PLAYLIST: (PodcastPlaylist, 'Podcast Playlist'),
|
|
Bookmark.ServiceChoices.HADITH: (Hadis, 'Hadith'),
|
|
Bookmark.ServiceChoices.HADITH_CORRECTION: (HadisCorrection, 'Hadith Correction'),
|
|
Bookmark.ServiceChoices.VIDEO: (Video, 'Video'),
|
|
Bookmark.ServiceChoices.VIDEO_PLAYLIST: (VideoPlaylist, 'Video Playlist'),
|
|
Bookmark.ServiceChoices.ARTICLE: (Article, 'Article'),
|
|
}
|
|
|
|
total_checked = Bookmark.objects.count()
|
|
self.stdout.write(f"Scanning {total_checked} total bookmark(s)...")
|
|
|
|
total_orphans = 0
|
|
orphan_ids_to_delete = []
|
|
|
|
for service_choice, (model_cls, label) in service_model_map.items():
|
|
bms = Bookmark.objects.filter(service=service_choice)
|
|
content_ids = set(bms.values_list('content_id', flat=True))
|
|
if not content_ids:
|
|
continue
|
|
|
|
existing_ids = set(model_cls.objects.filter(id__in=content_ids).values_list('id', flat=True))
|
|
missing_ids = content_ids - existing_ids
|
|
|
|
if missing_ids:
|
|
orphan_bms = bms.filter(content_id__in=missing_ids)
|
|
count = orphan_bms.count()
|
|
total_orphans += count
|
|
ids = list(orphan_bms.values_list('id', flat=True))
|
|
orphan_ids_to_delete.extend(ids)
|
|
self.stdout.write(
|
|
self.style.WARNING(f" - [{label} ({service_choice})]: found {count} orphan bookmark(s) (missing IDs: {sorted(list(missing_ids))[:10]}{'...' if len(missing_ids) > 10 else ''})")
|
|
)
|
|
|
|
# Check any bookmarks with unknown service choices
|
|
known_services = set(service_model_map.keys())
|
|
unknown_bms = Bookmark.objects.exclude(service__in=known_services)
|
|
unknown_count = unknown_bms.count()
|
|
if unknown_count > 0:
|
|
total_orphans += unknown_count
|
|
orphan_ids_to_delete.extend(list(unknown_bms.values_list('id', flat=True)))
|
|
self.stdout.write(
|
|
self.style.WARNING(f" - [Unknown Service]: found {unknown_count} bookmark(s) with invalid service name.")
|
|
)
|
|
|
|
if total_orphans == 0:
|
|
self.stdout.write(self.style.SUCCESS("No orphan bookmarks found! Everything is clean."))
|
|
return
|
|
|
|
self.stdout.write(f"\nTotal orphan bookmark(s) found: {total_orphans}")
|
|
|
|
if dry_run:
|
|
self.stdout.write(self.style.NOTICE("Dry run enabled. No bookmarks were deleted."))
|
|
else:
|
|
deleted_count, _ = Bookmark.objects.filter(id__in=orphan_ids_to_delete).delete()
|
|
self.stdout.write(self.style.SUCCESS(f"Successfully deleted {deleted_count} orphan bookmark(s)!"))
|