From 515ce7f791ccebd28b72a58ce1b2dc02cd23ef2c Mon Sep 17 00:00:00 2001 From: Hassan Raza Date: Mon, 1 Sep 2025 13:08:43 +0500 Subject: [PATCH] chore: Management command to update preferences (#37297) --- .../update_notification_preferences.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 openedx/core/djangoapps/notifications/management/commands/update_notification_preferences.py diff --git a/openedx/core/djangoapps/notifications/management/commands/update_notification_preferences.py b/openedx/core/djangoapps/notifications/management/commands/update_notification_preferences.py new file mode 100644 index 0000000000..2653e8b1cd --- /dev/null +++ b/openedx/core/djangoapps/notifications/management/commands/update_notification_preferences.py @@ -0,0 +1,116 @@ +""" +Management command for updating notification preferences with parameters +""" +import logging + +from django.core.management.base import BaseCommand, CommandError +from django.db import transaction + +from openedx.core.djangoapps.notifications.models import NotificationPreference +from openedx.core.djangoapps.notifications.base_notification import ( + COURSE_NOTIFICATION_APPS, + COURSE_NOTIFICATION_TYPES +) + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Management command to update boolean notification preferences. + + This command updates channel (`web`, `push`, `email`) + in the NotificationPreference model for a given app and type. + + Features: + - Requires `app` and `type`, validated against + COURSE_NOTIFICATION_APPS and COURSE_NOTIFICATION_TYPES. + - Allows updating a single channel to `true` or `false`. + - Supports optional `--user_ids` argument to limit updates + to specific users. + - Provides a `--dry-run` mode to preview changes without + committing to the database. + - Logs the number of affected records and affected user IDs. + + Example usage: + python manage.py update_notification_preference discussion new_comment_on_response email false + python manage.py update_notification_preference discussion new_comment_on_response push false --user_ids 5 7 12 + python manage.py update_notification_preference discussion new_comment_on_response web false --dry-run + """ + help = "Update boolean notification preferences for users at account level." + + def add_arguments(self, parser): + parser.add_argument( + "app", + type=str, + choices=list(COURSE_NOTIFICATION_APPS.keys()), + help=f"App key (choices: {', '.join(COURSE_NOTIFICATION_APPS.keys())})", + ) + parser.add_argument( + "type", + type=str, + choices=list(COURSE_NOTIFICATION_TYPES.keys()), + help=f"Type key (choices: {', '.join(COURSE_NOTIFICATION_TYPES.keys())})" + ) + parser.add_argument( + "channel", + type=str, + choices=["web", "push", "email"], + help="channel to update" + ) + parser.add_argument( + "value", + type=str, + choices=["true", "false"], + help="Boolean value (true/false)" + ) + parser.add_argument( + "--user_ids", + nargs="+", + type=int, + help="Optional list of user IDs to update only", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Simulate update without saving changes", + ) + + def handle(self, *args, **options): + app = options["app"] + pref_type = options["type"] + channel = options["channel"] + value_str = options["value"].lower() + dry_run = options["dry_run"] + user_ids = options.get("user_ids") + + if value_str in ["true"]: + new_value = True + elif value_str in ["false"]: + new_value = False + else: + raise CommandError("Value must be true/false") + + queryset = NotificationPreference.objects.filter(app=app, type=pref_type) + if user_ids: + queryset = queryset.filter(user_id__in=user_ids) + + queryset = queryset.exclude(**{channel: new_value}) # only ones that need updating + + affected = queryset.count() + + if not affected: + logger.info("No records to update.") + return + + logger.info( + f"{affected} record(s) will be updated. " + ) + + if dry_run: + logger.info("Dry-run mode: no changes applied.") + return + + with transaction.atomic(): + updated = queryset.update(**{channel: new_value}) + logger.info(f" Updated {updated} records.")