Skip to content

Commit 0f844cf

Browse files
committed
Rework notification subscriptions de-duplication command
1 parent 52ad0ef commit 0f844cf

File tree

1 file changed

+39
-24
lines changed

1 file changed

+39
-24
lines changed

osf/management/commands/remove_duplicate_notification_subscriptions.py

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77

88
class Command(BaseCommand):
99
help = (
10-
'Remove duplicate NotificationSubscription records, keeping only '
11-
'the highest-id record per (user, content_type, object_id, notification_type).'
10+
'Remove duplicate NotificationSubscription records, keeping only the highest-id record: '
11+
'Default uniqueness: (user, content_type, object_id, notification_type, is_digest); '
12+
'Optional uniqueness with --exclude-is-digest: (user, content_type, object_id, notification_type).'
1213
)
1314

1415
def add_arguments(self, parser):
@@ -17,39 +18,53 @@ def add_arguments(self, parser):
1718
action='store_true',
1819
help='Show how many rows would be deleted without deleting anything.',
1920
)
21+
parser.add_argument(
22+
'--exclude-is-digest',
23+
action='store_true',
24+
default=False,
25+
help='Whether to exclude _is_digest field in unique_together')
2026

2127
def handle(self, *args, **options):
28+
2229
self.stdout.write('Finding duplicate NotificationSubscription records…')
2330

24-
to_remove = NotificationSubscription.objects.filter(
25-
Exists(
26-
NotificationSubscription.objects.filter(
27-
user_id=OuterRef('user_id'),
28-
content_type_id=OuterRef('content_type_id'),
29-
object_id=OuterRef('object_id'),
30-
notification_type_id=OuterRef('notification_type_id'),
31-
_is_digest=OuterRef('_is_digest'),
32-
id__gt=OuterRef('id'), # keep most recent record
31+
if options['exclude_is_digest']:
32+
to_remove = NotificationSubscription.objects.filter(
33+
Exists(
34+
NotificationSubscription.objects.filter(
35+
user_id=OuterRef('user_id'),
36+
content_type_id=OuterRef('content_type_id'),
37+
object_id=OuterRef('object_id'),
38+
notification_type_id=OuterRef('notification_type_id'),
39+
id__gt=OuterRef('id'), # keep most recent record
40+
)
41+
)
42+
)
43+
else:
44+
to_remove = NotificationSubscription.objects.filter(
45+
Exists(
46+
NotificationSubscription.objects.filter(
47+
user_id=OuterRef('user_id'),
48+
content_type_id=OuterRef('content_type_id'),
49+
object_id=OuterRef('object_id'),
50+
notification_type_id=OuterRef('notification_type_id'),
51+
_is_digest=OuterRef('_is_digest'),
52+
id__gt=OuterRef('id'), # keep most recent record
53+
)
3354
)
3455
)
35-
)
3656

3757
count = to_remove.count()
3858
self.stdout.write(f"Duplicates to remove: {count}")
3959

40-
if options['dry']:
41-
self.stdout.write(
42-
self.style.WARNING('Dry run enabled — no records were deleted.')
43-
)
44-
return
45-
4660
if count == 0:
4761
self.stdout.write(self.style.SUCCESS('No duplicates found.'))
48-
return
4962

50-
with transaction.atomic():
51-
deleted, _ = to_remove.delete()
63+
if options['dry']:
64+
self.stdout.write(self.style.WARNING('Dry run enabled — no records were deleted.'))
65+
return
5266

53-
self.stdout.write(
54-
self.style.SUCCESS(f"Successfully removed {deleted} duplicate records.")
55-
)
67+
if count > 0:
68+
with transaction.atomic():
69+
deleted, _ = to_remove.delete()
70+
self.stdout.write(self.style.SUCCESS(f"Successfully removed {deleted} duplicate records."))

0 commit comments

Comments
 (0)