From c9d20bf4b49962f38f6f086cc80d5026e8c16832 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 17 Jun 2026 11:16:03 +0000 Subject: [PATCH 1/8] feat: replace user identity strings with user IDs --- cms/djangoapps/course_creators/admin.py | 12 ++++-- common/djangoapps/student/emails.py | 10 ++++- .../student/models/course_enrollment.py | 10 ++++- common/djangoapps/student/models/user.py | 5 ++- common/djangoapps/student/views/management.py | 37 +++++++++++++++---- lms/djangoapps/bulk_user_retirement/views.py | 11 +++++- .../commands/goal_reminder_email.py | 5 ++- lms/djangoapps/courseware/model_data.py | 8 +++- lms/djangoapps/courseware/views/views.py | 5 ++- lms/djangoapps/instructor/views/api.py | 5 ++- .../views/instructor_task_helpers.py | 10 ++++- .../commands/manual_verifications.py | 21 +++++++++-- .../core/djangoapps/user_authn/views/login.py | 5 ++- 13 files changed, 115 insertions(+), 29 deletions(-) diff --git a/cms/djangoapps/course_creators/admin.py b/cms/djangoapps/course_creators/admin.py index 75f29c6ca669..5f9f713f0213 100644 --- a/cms/djangoapps/course_creators/admin.py +++ b/cms/djangoapps/course_creators/admin.py @@ -158,8 +158,11 @@ def send_user_notification_callback(sender, **kwargs): # pylint: disable=unused try: user.email_user(subject, message, studio_request_email) - except: # pylint: disable=bare-except - log.warning("Unable to send course creator status e-mail to %s", user.email) + except: # lint-amnesty, pylint: disable=bare-except + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning("Unable to send course creator status e-mail to user ID %s", user.id) + else: + log.warning("Unable to send course creator status e-mail to %s", user.email) @receiver(send_admin_notification, sender=CourseCreator) @@ -185,7 +188,10 @@ def send_admin_notification_callback(sender, **kwargs): # pylint: disable=unuse fail_silently=False ) except SMTPException: - log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning("Failure sending 'pending state' e-mail for user ID %s to [REDACTED]", user.id) + else: + log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email) @receiver(m2m_changed, sender=CourseCreator.organizations.through) diff --git a/common/djangoapps/student/emails.py b/common/djangoapps/student/emails.py index 6b4641722de6..91cb340fee1c 100644 --- a/common/djangoapps/student/emails.py +++ b/common/djangoapps/student/emails.py @@ -27,8 +27,14 @@ def send_proctoring_requirements_email(context): user_context={'full_name': user.profile.name} ) ace.send(msg) - log.info('Proctoring requirements email sent to user: %r', user.username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info('Proctoring requirements email sent to user ID: %r', user.id) + else: + log.info('Proctoring requirements email sent to user: %r', user.username) return True except Exception: # pylint: disable=broad-except - log.exception('Could not send email for proctoring requirements to user %s', user.username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.exception('Could not send email for proctoring requirements to user ID %s', user.id) + else: + log.exception('Could not send email for proctoring requirements to user %s', user.username) return False diff --git a/common/djangoapps/student/models/course_enrollment.py b/common/djangoapps/student/models/course_enrollment.py index cbf257b77f46..4c3f3f76f200 100644 --- a/common/djangoapps/student/models/course_enrollment.py +++ b/common/djangoapps/student/models/course_enrollment.py @@ -738,8 +738,11 @@ def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=Fal course_key=course_key, ) if check_access: - log.warning("User %s failed to enroll in non-existent course %s", user.username, str(course_key)) - raise NonExistentCourseError # pylint: disable=raise-missing-from # noqa: B904 + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning("User %s failed to enroll in non-existent course %s", user.id, str(course_key)) + else: + log.warning("User %s failed to enroll in non-existent course %s", user.username, str(course_key)) + raise NonExistentCourseError # lint-amnesty, pylint: disable=raise-missing-from if check_access: if cls.is_enrollment_closed(user, course) and not can_upgrade: @@ -829,6 +832,9 @@ def enroll_by_email(cls, email, course_id, mode=None, ignore_errors=True): return cls.enroll(user, course_id, mode) except User.DoesNotExist: err_msg = "Tried to enroll email {} into course {}, but user not found" + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error(err_msg.format('[PII_REDACTED]', course_id)) + else: log.error(err_msg.format(email, course_id)) if ignore_errors: return None diff --git a/common/djangoapps/student/models/user.py b/common/djangoapps/student/models/user.py index 4789f0e47c38..c5c4edbd7422 100644 --- a/common/djangoapps/student/models/user.py +++ b/common/djangoapps/student/models/user.py @@ -887,7 +887,10 @@ def activate(self): # pylint: disable=missing-function-docstring self.activation_timestamp = datetime.utcnow() self.save() USER_ACCOUNT_ACTIVATED.send_robust(self.__class__, user=self.user) - log.info('User %s (%s) account is successfully activated.', self.user.username, self.user.email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info('User %s account is successfully activated.', self.user.id) + else: + log.info('User %s (%s) account is successfully activated.', self.user.username, self.user.email) class PendingNameChange(DeletableByUserValue, models.Model): # noqa: DJ008 diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 5609afd49ddc..3638fc15a9f1 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -489,7 +489,10 @@ def change_enrollment(request, check_access=True): except UnenrollmentNotAllowed as exc: return HttpResponseBadRequest(str(exc)) - log.info("User %s unenrolled from %s; sending REFUND_ORDER", user.username, course_id) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("User %s unenrolled from %s; sending REFUND_ORDER", user.id, course_id) + else: + log.info("User %s unenrolled from %s; sending REFUND_ORDER", user.username, course_id) REFUND_ORDER.send(sender=None, course_enrollment=enrollment) return HttpResponse() else: @@ -557,11 +560,17 @@ def disable_account_ajax(request): if account_action == 'disable': user_account.account_status = UserStanding.ACCOUNT_DISABLED context['message'] = _("Successfully disabled {}'s account").format(username) - log.info("%s disabled %s's account", request.user, username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("%s disabled user %s's account", request.user.id, '[PII_REDACTED]') + else: + log.info("%s disabled %s's account", request.user, username) elif account_action == 'reenable': user_account.account_status = UserStanding.ACCOUNT_ENABLED context['message'] = _("Successfully reenabled {}'s account").format(username) - log.info("%s reenabled %s's account", request.user, username) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("%s reenabled user %s's account", request.user.id, '[PII_REDACTED]') + else: + log.info("%s reenabled %s's account", request.user, username) else: context['message'] = _("Unexpected account status") return JsonResponse(context, status=400) @@ -847,11 +856,17 @@ def do_email_change_request(user, new_email, activation_key=None, secondary_emai try: ace.send(msg) - log.info("Email activation link sent to user [%s].", new_email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("Email activation link sent for user ID: [%s].", user.id) + else: + log.info("Email activation link sent to user [%s].", new_email) except Exception: from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) - log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) - raise ValueError(_('Unable to send email activation link. Please try again later.')) # pylint: disable=raise-missing-from # noqa: B904 + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Unable to send email activation link from a redacted address', exc_info=True) + else: + log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) + raise ValueError(_('Unable to send email activation link. Please try again later.')) # lint-amnesty, pylint: disable=raise-missing-from if not secondary_email_change_request: # When the email address change is complete, a "edx.user.settings.changed" event will be emitted. @@ -960,7 +975,10 @@ def confirm_email_change(request, key): try: ace.send(msg) except Exception: # pylint: disable=broad-except - log.warning('Unable to send confirmation email to old address', exc_info=True) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning('Unable to send confirmation email to old address [REDACTED]', exc_info=True) + else: + log.warning('Unable to send confirmation email to old address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) transaction.set_rollback(True) return response @@ -976,7 +994,10 @@ def confirm_email_change(request, key): try: ace.send(msg) except Exception: # pylint: disable=broad-except - log.warning('Unable to send confirmation email to new address', exc_info=True) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning('Unable to send confirmation email to new address [REDACTED]', exc_info=True) + else: + log.warning('Unable to send confirmation email to new address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) transaction.set_rollback(True) return response diff --git a/lms/djangoapps/bulk_user_retirement/views.py b/lms/djangoapps/bulk_user_retirement/views.py index 28f23651fb65..32b2a4c60d4b 100644 --- a/lms/djangoapps/bulk_user_retirement/views.py +++ b/lms/djangoapps/bulk_user_retirement/views.py @@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model from django.db import transaction +from django.conf import settings from rest_framework import permissions, status from rest_framework.response import Response from rest_framework.views import APIView @@ -56,11 +57,17 @@ def post(self, request, **kwargs): # pylint: disable=unused-argument user_to_retire = User.objects.get(username=username) with transaction.atomic(): create_retirement_request_and_deactivate_account(user_to_retire) - log.info(f'The user "{username}" has been added to the retirement pipeline \ + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info('A user has been added to the retirement pipeline') + else: + log.info(f'The user "{username}" has been added to the retirement pipeline \') by "{request.user}"') except User.DoesNotExist: - log.exception(f'The user "{username}" does not exist.') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.exception('A user does not exist for bulk retirement.') + else: + log.exception(f'The user "{username}" does not exist.') failed_user_retirements.append(username) except Exception as exc: # pylint: disable=broad-except diff --git a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py index b9aac5e8efaa..e89f419d46a4 100644 --- a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py @@ -50,7 +50,10 @@ def send_ace_message(goal, session_id): """ user = goal.user if not user.has_usable_password(): - log.info(f'Goal Reminder User is disabled {user.username} course {goal.course_key}') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info(f'Goal Reminder User is disabled user ID {user.id} course {goal.course_key}') + else: + log.info(f'Goal Reminder User is disabled {user.username} course {goal.course_key}') return False try: course = CourseOverview.get_from_id(goal.course_key) diff --git a/lms/djangoapps/courseware/model_data.py b/lms/djangoapps/courseware/model_data.py index ed5cde367ae1..e881cbb453a6 100644 --- a/lms/djangoapps/courseware/model_data.py +++ b/lms/djangoapps/courseware/model_data.py @@ -28,6 +28,7 @@ from collections import defaultdict, namedtuple from django.db import DatabaseError, IntegrityError, transaction +from django.conf import settings from opaque_keys.edx.asides import AsideUsageKeyV1, AsideUsageKeyV2 from opaque_keys.edx.block_types import BlockTypeKeyV1 from opaque_keys.edx.keys import LearningContextKey @@ -405,8 +406,11 @@ def set_many(self, kv_dict): pending_updates ) except DatabaseError: - log.exception("Saving user state failed for %s", self.user.username) - raise KeyValueMultiSaveError([]) # pylint: disable=raise-missing-from # noqa: B904 + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.exception("Saving user state failed for user ID %s", self.user.id) + else: + log.exception("Saving user state failed for %s", self.user.username) + raise KeyValueMultiSaveError([]) # lint-amnesty, pylint: disable=raise-missing-from finally: self._cache.update(pending_updates) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 960be2fd7849..39b08cb3e53a 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -1479,7 +1479,10 @@ def generate_user_cert(request, course_id): return HttpResponseBadRequest(str(e)) if not is_course_passed(student, course): - log.info("User %s has not passed the course: %s", student.username, course_id) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info("User ID %s has not passed the course: %s", student.id, course_id) + else: + log.info("User %s has not passed the course: %s", student.username, course_id) return HttpResponseBadRequest(_("Your certificate will be available when you pass the course.")) certificate_status = certs_api.certificate_downloadable_status(student, course.id) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 9b629fd807c7..44dadf23ddec 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -462,7 +462,10 @@ def post(self, request, course_id): # pylint: disable=too-many-statements warnings.append({ 'username': username, 'email': email, 'response': warning_message }) - log.warning('email %s already exist', email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning('email [REDACTED] already exist') + else: + log.warning('email %s already exist', email) else: log.info( "user already exists with username '%s' and email '%s'", diff --git a/lms/djangoapps/instructor/views/instructor_task_helpers.py b/lms/djangoapps/instructor/views/instructor_task_helpers.py index e50687da3a77..904c0784f58e 100644 --- a/lms/djangoapps/instructor/views/instructor_task_helpers.py +++ b/lms/djangoapps/instructor/views/instructor_task_helpers.py @@ -10,6 +10,8 @@ from django.utils.translation import gettext as _ from django.utils.translation import ngettext +from django.conf import settings + from lms.djangoapps.bulk_email.models import CourseEmail from lms.djangoapps.instructor_task.models import InstructorTaskSchedule from lms.djangoapps.instructor_task.views import get_task_completion_info @@ -55,7 +57,10 @@ def extract_email_features(email_task): try: task_input_information = json.loads(email_task.task_input) except ValueError: - log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error("Could not parse task input as valid json; task input: [REDACTED]") + else: + log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) return email_error_information() email = CourseEmail.objects.get(id=task_input_information['email_id']) @@ -82,6 +87,9 @@ def extract_email_features(email_task): try: task_output = json.loads(email_task.task_output) except ValueError: + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error("Could not parse task output as valid json; task output: [REDACTED]") + else: log.error("Could not parse task output as valid json; task output: %s", email_task.task_output) else: if 'succeeded' in task_output and task_output['succeeded'] > 0: diff --git a/lms/djangoapps/verify_student/management/commands/manual_verifications.py b/lms/djangoapps/verify_student/management/commands/manual_verifications.py index bb84fabb2713..bdaf82f0b4d9 100644 --- a/lms/djangoapps/verify_student/management/commands/manual_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/manual_verifications.py @@ -9,6 +9,7 @@ from django.contrib.auth.models import User # pylint: disable=imported-auth-user from django.core.management.base import BaseCommand, CommandError +from django.conf import settings from lms.djangoapps.verify_student.models import ManualVerification from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date @@ -53,7 +54,10 @@ def handle(self, *args, **options): if single_email: successfully_verified = self._add_user_to_manual_verification(single_email) if successfully_verified is False: - log.error(f'Manual verification of {single_email} failed') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Manual verification of [REDACTED_EMAIL] failed') + else: + log.error(f'Manual verification of {single_email} failed') return email_ids_file = options['email_ids_file'] @@ -70,7 +74,10 @@ def handle(self, *args, **options): len(failed_emails), total_emails )) - log.error(f'Failed emails:{pformat(failed_emails)}') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Failed emails: [REDACTED]') + else: + log.error(f'Failed emails:{pformat(failed_emails)}') else: log.info(f'Successfully generated manual verification for {total_emails} emails.') @@ -122,7 +129,10 @@ def _add_users_to_manual_verification(self, email_ids): status='approved', )) else: - log.info(f'Skipping email {user.email}, existing verification found.') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info(f'Skipping user ID {user.id}, existing verification found.') + else: + log.info(f'Skipping email {user.email}, existing verification found.') ManualVerification.objects.bulk_create(verifications_to_create) failed_emails = set(email_ids) - set(users.values_list('email', flat=True)) return list(failed_emails) @@ -147,5 +157,8 @@ def _add_user_to_manual_verification(self, email_id): ) return True except User.DoesNotExist: - log.error(f'Tried to verify email {email_id}, but user not found') + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error('Tried to verify email [REDACTED_EMAIL], but user not found') + else: + log.error(f'Tried to verify email {email_id}, but user not found') return False diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py index 6a012d8d212d..9f06322e5af0 100644 --- a/openedx/core/djangoapps/user_authn/views/login.py +++ b/openedx/core/djangoapps/user_authn/views/login.py @@ -199,7 +199,10 @@ def _enforce_password_policy_compliance(request, user): # pylint: disable=missi if LoginFailures.is_feature_enabled(): LoginFailures.increment_lockout_counter(user) - AUDIT_LOG.info("Password reset initiated for email %s.", user.email) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + AUDIT_LOG.info("Password reset initiated for user ID %s.", user.id) + else: + AUDIT_LOG.info("Password reset initiated for email %s.", user.email) tracker.emit( PASSWORD_RESET_INITIATED, { From a06f530e363ef026dba36118cac13a4fcb138f45 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Thu, 18 Jun 2026 07:19:50 +0000 Subject: [PATCH 2/8] fix: corrected lint error --- common/djangoapps/student/models/course_enrollment.py | 6 +++--- .../management/commands/manual_verifications.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/djangoapps/student/models/course_enrollment.py b/common/djangoapps/student/models/course_enrollment.py index 4c3f3f76f200..82c887167a7d 100644 --- a/common/djangoapps/student/models/course_enrollment.py +++ b/common/djangoapps/student/models/course_enrollment.py @@ -833,9 +833,9 @@ def enroll_by_email(cls, email, course_id, mode=None, ignore_errors=True): except User.DoesNotExist: err_msg = "Tried to enroll email {} into course {}, but user not found" if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error(err_msg.format('[PII_REDACTED]', course_id)) - else: - log.error(err_msg.format(email, course_id)) + log.error(err_msg.format('[PII_REDACTED]', course_id)) + else: + log.error(err_msg.format(email, course_id)) if ignore_errors: return None raise diff --git a/lms/djangoapps/verify_student/management/commands/manual_verifications.py b/lms/djangoapps/verify_student/management/commands/manual_verifications.py index bdaf82f0b4d9..73f3f90bb515 100644 --- a/lms/djangoapps/verify_student/management/commands/manual_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/manual_verifications.py @@ -7,9 +7,9 @@ import os from pprint import pformat +from django.conf import settings from django.contrib.auth.models import User # pylint: disable=imported-auth-user from django.core.management.base import BaseCommand, CommandError -from django.conf import settings from lms.djangoapps.verify_student.models import ManualVerification from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date From 35f8781393b39ea4ce48697ce708a34132a3f2b0 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 19 Jun 2026 06:13:59 +0000 Subject: [PATCH 3/8] fix: resolved pylint issue --- lms/djangoapps/courseware/model_data.py | 2 +- .../instructor/views/instructor_task_helpers.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/courseware/model_data.py b/lms/djangoapps/courseware/model_data.py index e881cbb453a6..dcd4c16bc6ef 100644 --- a/lms/djangoapps/courseware/model_data.py +++ b/lms/djangoapps/courseware/model_data.py @@ -27,8 +27,8 @@ from abc import ABCMeta, abstractmethod from collections import defaultdict, namedtuple -from django.db import DatabaseError, IntegrityError, transaction from django.conf import settings +from django.db import DatabaseError, IntegrityError, transaction from opaque_keys.edx.asides import AsideUsageKeyV1, AsideUsageKeyV2 from opaque_keys.edx.block_types import BlockTypeKeyV1 from opaque_keys.edx.keys import LearningContextKey diff --git a/lms/djangoapps/instructor/views/instructor_task_helpers.py b/lms/djangoapps/instructor/views/instructor_task_helpers.py index 904c0784f58e..e45edfa5d5a3 100644 --- a/lms/djangoapps/instructor/views/instructor_task_helpers.py +++ b/lms/djangoapps/instructor/views/instructor_task_helpers.py @@ -87,10 +87,10 @@ def extract_email_features(email_task): try: task_output = json.loads(email_task.task_output) except ValueError: - if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error("Could not parse task output as valid json; task output: [REDACTED]") - else: - log.error("Could not parse task output as valid json; task output: %s", email_task.task_output) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.error("Could not parse task output as valid json; task output: [REDACTED]") + else: + log.error("Could not parse task output as valid json; task output: %s", email_task.task_output) else: if 'succeeded' in task_output and task_output['succeeded'] > 0: num_emails = task_output['succeeded'] From b426fd341ff506c89cf58b1b0c8533274f6335b2 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 19 Jun 2026 06:29:12 +0000 Subject: [PATCH 4/8] fix: lint issue fixed --- lms/djangoapps/bulk_user_retirement/views.py | 6 ++++-- lms/djangoapps/instructor/views/instructor_task_helpers.py | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/bulk_user_retirement/views.py b/lms/djangoapps/bulk_user_retirement/views.py index 32b2a4c60d4b..ec1ddc4f38f1 100644 --- a/lms/djangoapps/bulk_user_retirement/views.py +++ b/lms/djangoapps/bulk_user_retirement/views.py @@ -60,8 +60,10 @@ def post(self, request, **kwargs): # pylint: disable=unused-argument if settings.FEATURES['SQUELCH_PII_IN_LOGS']: log.info('A user has been added to the retirement pipeline') else: - log.info(f'The user "{username}" has been added to the retirement pipeline \') - by "{request.user}"') + log.info('The user "%s" has been added to the retirement pipeline by "%s"', + username, + request.user, + ) except User.DoesNotExist: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: diff --git a/lms/djangoapps/instructor/views/instructor_task_helpers.py b/lms/djangoapps/instructor/views/instructor_task_helpers.py index e45edfa5d5a3..f9d17e37e95d 100644 --- a/lms/djangoapps/instructor/views/instructor_task_helpers.py +++ b/lms/djangoapps/instructor/views/instructor_task_helpers.py @@ -7,11 +7,10 @@ import json import logging +from django.conf import settings from django.utils.translation import gettext as _ from django.utils.translation import ngettext -from django.conf import settings - from lms.djangoapps.bulk_email.models import CourseEmail from lms.djangoapps.instructor_task.models import InstructorTaskSchedule from lms.djangoapps.instructor_task.views import get_task_completion_info From b08bbd7d49dc8562de0c56d30516e29d8820397d Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 19 Jun 2026 06:44:51 +0000 Subject: [PATCH 5/8] fix: lint issue fixed --- common/djangoapps/student/models/course_enrollment.py | 4 ++-- common/djangoapps/student/views/management.py | 4 ++-- lms/djangoapps/bulk_user_retirement/views.py | 2 +- lms/djangoapps/courseware/model_data.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/djangoapps/student/models/course_enrollment.py b/common/djangoapps/student/models/course_enrollment.py index 82c887167a7d..f422f627b844 100644 --- a/common/djangoapps/student/models/course_enrollment.py +++ b/common/djangoapps/student/models/course_enrollment.py @@ -731,7 +731,7 @@ def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=Fal course_key=course.id, display_name=course.display_name, ) - except CourseOverview.DoesNotExist: + except CourseOverview.DoesNotExist as err: # This is here to preserve legacy behavior which allowed enrollment in courses # announced before the start of content creation. course_data = CourseData( @@ -742,7 +742,7 @@ def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=Fal log.warning("User %s failed to enroll in non-existent course %s", user.id, str(course_key)) else: log.warning("User %s failed to enroll in non-existent course %s", user.username, str(course_key)) - raise NonExistentCourseError # lint-amnesty, pylint: disable=raise-missing-from + raise NonExistentCourseError from err # lint-amnesty, pylint: disable=raise-missing-from if check_access: if cls.is_enrollment_closed(user, course) and not can_upgrade: diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 3638fc15a9f1..aff76e116c81 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -860,13 +860,13 @@ def do_email_change_request(user, new_email, activation_key=None, secondary_emai log.info("Email activation link sent for user ID: [%s].", user.id) else: log.info("Email activation link sent to user [%s].", new_email) - except Exception: + except Exception as err: from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: log.error('Unable to send email activation link from a redacted address', exc_info=True) else: log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) - raise ValueError(_('Unable to send email activation link. Please try again later.')) # lint-amnesty, pylint: disable=raise-missing-from + raise ValueError(_('Unable to send email activation link. Please try again later.')) from err # lint-amnesty, pylint: disable=raise-missing-from if not secondary_email_change_request: # When the email address change is complete, a "edx.user.settings.changed" event will be emitted. diff --git a/lms/djangoapps/bulk_user_retirement/views.py b/lms/djangoapps/bulk_user_retirement/views.py index ec1ddc4f38f1..69219226d067 100644 --- a/lms/djangoapps/bulk_user_retirement/views.py +++ b/lms/djangoapps/bulk_user_retirement/views.py @@ -3,9 +3,9 @@ """ import logging +from django.conf import settings from django.contrib.auth import get_user_model from django.db import transaction -from django.conf import settings from rest_framework import permissions, status from rest_framework.response import Response from rest_framework.views import APIView diff --git a/lms/djangoapps/courseware/model_data.py b/lms/djangoapps/courseware/model_data.py index dcd4c16bc6ef..53447dde1b92 100644 --- a/lms/djangoapps/courseware/model_data.py +++ b/lms/djangoapps/courseware/model_data.py @@ -405,12 +405,12 @@ def set_many(self, kv_dict): self.user.username, pending_updates ) - except DatabaseError: + except DatabaseError as err: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: log.exception("Saving user state failed for user ID %s", self.user.id) else: log.exception("Saving user state failed for %s", self.user.username) - raise KeyValueMultiSaveError([]) # lint-amnesty, pylint: disable=raise-missing-from + raise KeyValueMultiSaveError([]) from err # lint-amnesty, pylint: disable=raise-missing-from finally: self._cache.update(pending_updates) From 5e2a0f7d5b57a361ffb1822aacf2277fa633cdd6 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 19 Jun 2026 07:12:43 +0000 Subject: [PATCH 6/8] fix: lint issue fixed --- common/djangoapps/student/views/management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index aff76e116c81..c6441738496d 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -915,7 +915,7 @@ def activate_secondary_email(request, key): @ensure_csrf_cookie -def confirm_email_change(request, key): +def confirm_email_change(request, key): # pylint: disable=too-many-statements """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update From 780ae84b9db5c84492f3943ecd3ebddbeaa1ff5e Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 24 Jun 2026 04:43:08 +0000 Subject: [PATCH 7/8] fix: updated the log message with user id --- cms/djangoapps/course_creators/admin.py | 3 +- .../student/models/course_enrollment.py | 77 +++++++++++++------ common/djangoapps/student/views/management.py | 10 +-- lms/djangoapps/bulk_user_retirement/views.py | 5 +- lms/djangoapps/instructor/views/api.py | 15 ++-- .../views/instructor_task_helpers.py | 4 +- .../commands/manual_verifications.py | 6 +- 7 files changed, 79 insertions(+), 41 deletions(-) diff --git a/cms/djangoapps/course_creators/admin.py b/cms/djangoapps/course_creators/admin.py index 5f9f713f0213..adc621b24e36 100644 --- a/cms/djangoapps/course_creators/admin.py +++ b/cms/djangoapps/course_creators/admin.py @@ -172,6 +172,7 @@ def send_admin_notification_callback(sender, **kwargs): # pylint: disable=unuse """ user = kwargs['user'] + # studio_request_email is a system email address, not PII, which can safely be logged. studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '') context = {'user_name': user.username, 'user_email': user.email} @@ -189,7 +190,7 @@ def send_admin_notification_callback(sender, **kwargs): # pylint: disable=unuse ) except SMTPException: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.warning("Failure sending 'pending state' e-mail for user ID %s to [REDACTED]", user.id) + log.warning("Failure sending 'pending state' e-mail for user ID %s to %s", user.id, studio_request_email) else: log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email) diff --git a/common/djangoapps/student/models/course_enrollment.py b/common/djangoapps/student/models/course_enrollment.py index f422f627b844..d2a5aa3f7aad 100644 --- a/common/djangoapps/student/models/course_enrollment.py +++ b/common/djangoapps/student/models/course_enrollment.py @@ -664,12 +664,20 @@ def emit_event(self, event_name, enterprise_uuid=None): except Exception: # pylint: disable=broad-except if event_name and self.course_id: - log.exception( - 'Unable to emit event %s for user %s and course %s', - event_name, - self.user.username, - self.course_id, - ) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.exception( + 'Unable to emit event %s for user %s and course %s', + event_name, + self.user.id, + self.course_id, + ) + else: + log.exception( + 'Unable to emit event %s for user %s and course %s', + event_name, + self.user.username, + self.course_id, + ) @classmethod def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=False, enterprise_uuid=None): @@ -746,28 +754,51 @@ def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=Fal if check_access: if cls.is_enrollment_closed(user, course) and not can_upgrade: - log.warning( - "User %s failed to enroll in course %s because enrollment is closed (can_upgrade=%s).", - user.username, - str(course_key), - can_upgrade, - ) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning( + "User %s failed to enroll in course %s because enrollment is closed (can_upgrade=%s).", + user.id, + str(course_key), + can_upgrade, + ) + else: + log.warning( + "User %s failed to enroll in course %s because enrollment is closed (can_upgrade=%s).", + user.username, + str(course_key), + can_upgrade, + ) raise EnrollmentClosedError if cls.objects.is_course_full(course): + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.warning( + "Course %s has reached its maximum enrollment of %d learners. User %s failed to enroll.", + str(course_key), + course.max_student_enrollments_allowed, + user.id, + ) + else: + log.warning( + "Course %s has reached its maximum enrollment of %d learners. User %s failed to enroll.", + str(course_key), + course.max_student_enrollments_allowed, + user.username, + ) + raise CourseFullError + if cls.is_enrolled(user, course_key): + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: log.warning( - "Course %s has reached its maximum enrollment of %d learners. User %s failed to enroll.", - str(course_key), - course.max_student_enrollments_allowed, + "User %s attempted to enroll in %s, but they were already enrolled", + user.id, + str(course_key) + ) + else: + log.warning( + "User %s attempted to enroll in %s, but they were already enrolled", user.username, + str(course_key) ) - raise CourseFullError - if cls.is_enrolled(user, course_key): - log.warning( - "User %s attempted to enroll in %s, but they were already enrolled", - user.username, - str(course_key) - ) if check_access: raise AlreadyEnrolledError @@ -833,7 +864,7 @@ def enroll_by_email(cls, email, course_id, mode=None, ignore_errors=True): except User.DoesNotExist: err_msg = "Tried to enroll email {} into course {}, but user not found" if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error(err_msg.format('[PII_REDACTED]', course_id)) + log.error("Tried to enroll a redacted email into course %s, but user not found", course_id) else: log.error(err_msg.format(email, course_id)) if ignore_errors: diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index c6441738496d..3e35065ffdcd 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -561,14 +561,14 @@ def disable_account_ajax(request): user_account.account_status = UserStanding.ACCOUNT_DISABLED context['message'] = _("Successfully disabled {}'s account").format(username) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.info("%s disabled user %s's account", request.user.id, '[PII_REDACTED]') + log.info("User %s disabled user %s's account", request.user.id, user.id) else: log.info("%s disabled %s's account", request.user, username) elif account_action == 'reenable': user_account.account_status = UserStanding.ACCOUNT_ENABLED context['message'] = _("Successfully reenabled {}'s account").format(username) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.info("%s reenabled user %s's account", request.user.id, '[PII_REDACTED]') + log.info("User %s reenabled user %s's account", request.user.id, user.id) else: log.info("%s reenabled %s's account", request.user, username) else: @@ -863,7 +863,7 @@ def do_email_change_request(user, new_email, activation_key=None, secondary_emai except Exception as err: from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error('Unable to send email activation link from a redacted address', exc_info=True) + log.error('Unable to send email activation link for user %s from a redacted address', user.id, exc_info=True) else: log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) raise ValueError(_('Unable to send email activation link. Please try again later.')) from err # lint-amnesty, pylint: disable=raise-missing-from @@ -976,7 +976,7 @@ def confirm_email_change(request, key): # pylint: disable=too-many-statements ace.send(msg) except Exception: # pylint: disable=broad-except if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.warning('Unable to send confirmation email to old address [REDACTED]', exc_info=True) + log.warning('Unable to send confirmation email to old address for user %s', user.id, exc_info=True) else: log.warning('Unable to send confirmation email to old address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) @@ -995,7 +995,7 @@ def confirm_email_change(request, key): # pylint: disable=too-many-statements ace.send(msg) except Exception: # pylint: disable=broad-except if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.warning('Unable to send confirmation email to new address [REDACTED]', exc_info=True) + log.warning('Unable to send confirmation email to new address for user %s', user.id, exc_info=True) else: log.warning('Unable to send confirmation email to new address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) diff --git a/lms/djangoapps/bulk_user_retirement/views.py b/lms/djangoapps/bulk_user_retirement/views.py index 69219226d067..dcb655c5dddc 100644 --- a/lms/djangoapps/bulk_user_retirement/views.py +++ b/lms/djangoapps/bulk_user_retirement/views.py @@ -58,7 +58,10 @@ def post(self, request, **kwargs): # pylint: disable=unused-argument with transaction.atomic(): create_retirement_request_and_deactivate_account(user_to_retire) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.info('A user has been added to the retirement pipeline') + log.info('User %s added to retirement pipeline by user %s', + user_to_retire.id, + request.user.id + ) else: log.info('The user "%s" has been added to the retirement pipeline by "%s"', username, diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 44dadf23ddec..abdbdf73257c 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -463,15 +463,18 @@ def post(self, request, course_id): # pylint: disable=too-many-statements 'username': username, 'email': email, 'response': warning_message }) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.warning('email [REDACTED] already exist') + log.warning('email for user %s already exist', user.id) else: log.warning('email %s already exist', email) else: - log.info( - "user already exists with username '%s' and email '%s'", - username, - email - ) + if settings.FEATURES['SQUELCH_PII_IN_LOGS']: + log.info('user already exists with user ID %s', user.id) + else: + log.info( + "user already exists with username '%s' and email '%s'", + username, + email + ) # enroll a user if it is not already enrolled. if not is_user_enrolled_in_course(user, course_id): diff --git a/lms/djangoapps/instructor/views/instructor_task_helpers.py b/lms/djangoapps/instructor/views/instructor_task_helpers.py index f9d17e37e95d..9aedc2ef713e 100644 --- a/lms/djangoapps/instructor/views/instructor_task_helpers.py +++ b/lms/djangoapps/instructor/views/instructor_task_helpers.py @@ -57,7 +57,7 @@ def extract_email_features(email_task): task_input_information = json.loads(email_task.task_input) except ValueError: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error("Could not parse task input as valid json; task input: [REDACTED]") + log.error("Could not parse task input as valid json; task input is redacted") else: log.error("Could not parse task input as valid json; task input: %s", email_task.task_input) return email_error_information() @@ -87,7 +87,7 @@ def extract_email_features(email_task): task_output = json.loads(email_task.task_output) except ValueError: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error("Could not parse task output as valid json; task output: [REDACTED]") + log.error("Could not parse task output as valid json; task output is redacted") else: log.error("Could not parse task output as valid json; task output: %s", email_task.task_output) else: diff --git a/lms/djangoapps/verify_student/management/commands/manual_verifications.py b/lms/djangoapps/verify_student/management/commands/manual_verifications.py index 73f3f90bb515..8c0d77ace462 100644 --- a/lms/djangoapps/verify_student/management/commands/manual_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/manual_verifications.py @@ -55,7 +55,7 @@ def handle(self, *args, **options): successfully_verified = self._add_user_to_manual_verification(single_email) if successfully_verified is False: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error('Manual verification of [REDACTED_EMAIL] failed') + log.error('Manual verification of a redacted email failed') else: log.error(f'Manual verification of {single_email} failed') return @@ -75,7 +75,7 @@ def handle(self, *args, **options): total_emails )) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error('Failed emails: [REDACTED]') + log.error('Failed emails are redacted') else: log.error(f'Failed emails:{pformat(failed_emails)}') else: @@ -158,7 +158,7 @@ def _add_user_to_manual_verification(self, email_id): return True except User.DoesNotExist: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error('Tried to verify email [REDACTED_EMAIL], but user not found') + log.error('Tried to verify a redacted email, but user not found') else: log.error(f'Tried to verify email {email_id}, but user not found') return False From 546747c352401b9ae6e8688d52c93f2035abae3c Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 24 Jun 2026 06:10:11 +0000 Subject: [PATCH 8/8] fix: linter issue resolved --- common/djangoapps/student/views/management.py | 6 +++++- lms/djangoapps/bulk_user_retirement/views.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 3e35065ffdcd..c8f8a2930c8c 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -863,7 +863,11 @@ def do_email_change_request(user, new_email, activation_key=None, secondary_emai except Exception as err: from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.error('Unable to send email activation link for user %s from a redacted address', user.id, exc_info=True) + log.error( + 'Unable to send email activation link for user %s from a redacted address', + user.id, + exc_info=True, + ) else: log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True) raise ValueError(_('Unable to send email activation link. Please try again later.')) from err # lint-amnesty, pylint: disable=raise-missing-from diff --git a/lms/djangoapps/bulk_user_retirement/views.py b/lms/djangoapps/bulk_user_retirement/views.py index dcb655c5dddc..00ce5f092079 100644 --- a/lms/djangoapps/bulk_user_retirement/views.py +++ b/lms/djangoapps/bulk_user_retirement/views.py @@ -58,8 +58,8 @@ def post(self, request, **kwargs): # pylint: disable=unused-argument with transaction.atomic(): create_retirement_request_and_deactivate_account(user_to_retire) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: - log.info('User %s added to retirement pipeline by user %s', - user_to_retire.id, + log.info('User %s added to retirement pipeline by user %s', + user_to_retire.id, request.user.id ) else: