Skip to content

Commit 3bc77b9

Browse files
Fix subscription management and add command to remove duplicate notifications
1 parent 4b52580 commit 3bc77b9

File tree

5 files changed

+64
-5
lines changed

5 files changed

+64
-5
lines changed

api/providers/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ def update(self, instance, validated_data):
401401
return instance
402402

403403
try:
404-
provider.remove_from_group(instance, instance.permission_group, unsubscribe=False)
404+
provider.remove_from_group(instance, instance.permission_group, unsubscribe=True)
405405
except ValueError as e:
406406
raise ValidationError(str(e))
407407
provider.add_to_group(instance, perm_group)

notifications/tasks.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import itertools
22
from calendar import monthrange
3-
from datetime import date
3+
from datetime import date, datetime
44
from django.contrib.contenttypes.models import ContentType
55
from django.db import connection
66
from django.utils import timezone
@@ -205,10 +205,11 @@ def send_moderator_email_task(self, user_id, notification_ids, provider_content_
205205
if current_moderators is None or not current_moderators.user_set.filter(id=user.id).exists():
206206
current_admins = provider.get_group('admin')
207207
if current_admins is None or not current_admins.user_set.filter(id=user.id).exists():
208-
log_message(f"User is not a moderator for provider {provider._id} - skipping email")
208+
log_message(f"User is not a moderator for provider {provider._id} - notifications will be marked as sent.")
209209
email_task.status = 'FAILURE'
210210
email_task.error_message = f'User is not a moderator for provider {provider._id}'
211211
email_task.save()
212+
notifications_qs.update(sent=datetime(1000, 1, 1))
212213
return
213214

214215
additional_context = {}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from django.core.management.base import BaseCommand
2+
from django.db import transaction
3+
from django.db.models import OuterRef, Exists
4+
5+
from osf.models import NotificationSubscription
6+
7+
8+
class Command(BaseCommand):
9+
help = (
10+
'Remove duplicate NotificationSubscription records, keeping only '
11+
'the highest-id record per (user, content_type, object_id, notification_type).'
12+
)
13+
14+
def add_arguments(self, parser):
15+
parser.add_argument(
16+
'--dry',
17+
action='store_true',
18+
help='Show how many rows would be deleted without deleting anything.',
19+
)
20+
21+
def handle(self, *args, **options):
22+
self.stdout.write('Finding duplicate NotificationSubscription records…')
23+
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
33+
)
34+
)
35+
)
36+
37+
count = to_remove.count()
38+
self.stdout.write(f"Duplicates to remove: {count}")
39+
40+
if options['dry']:
41+
self.stdout.write(
42+
self.style.WARNING('Dry run enabled — no records were deleted.')
43+
)
44+
return
45+
46+
if count == 0:
47+
self.stdout.write(self.style.SUCCESS('No duplicates found.'))
48+
return
49+
50+
with transaction.atomic():
51+
deleted, _ = to_remove.delete()
52+
53+
self.stdout.write(
54+
self.style.SUCCESS(f"Successfully removed {deleted} duplicate records.")
55+
)

osf/models/mixins.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,7 @@ def add_to_group(self, user, group):
10871087
content_type=ContentType.objects.get_for_model(self, for_concrete_model=False),
10881088
object_id=self.id,
10891089
notification_type=subscription.instance,
1090+
message_frequency='instantly',
10901091
_is_digest=True
10911092
)
10921093

scripts/populate_notification_subscriptions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ def populate_notification_subscriptions():
7676
content_type=user_ct,
7777
object_id=user.id,
7878
defaults={
79-
'message_frequency': 'none',
80-
},
79+
'_is_digest': True,
80+
'message_frequency': 'none',
81+
},
8182
)
8283
if is_created:
8384
created += 1
@@ -96,6 +97,7 @@ def populate_notification_subscriptions():
9697
content_type=node_ct,
9798
object_id=node.id,
9899
defaults={
100+
'_is_digest': True,
99101
'message_frequency': 'none',
100102
},
101103
)

0 commit comments

Comments
 (0)