diff --git a/futurex_openedx_extensions/helpers/signals.py b/futurex_openedx_extensions/helpers/signals.py index f52af8a8..f2eba716 100644 --- a/futurex_openedx_extensions/helpers/signals.py +++ b/futurex_openedx_extensions/helpers/signals.py @@ -1,11 +1,12 @@ """Signals for the futurex_openedx_extensions app""" from __future__ import annotations +import logging from typing import Any -from common.djangoapps.student.models import CourseAccessRole +from common.djangoapps.student.models import CourseAccessRole, UserProfile from django.core.cache import cache -from django.db.models.signals import post_delete, post_save +from django.db.models.signals import post_delete, post_save, pre_save from django.dispatch import receiver from futurex_openedx_extensions.helpers import constants as cs @@ -17,6 +18,8 @@ ) from futurex_openedx_extensions.helpers.tenants import get_all_tenant_ids, get_all_tenants_info +logger = logging.getLogger(__name__) + @receiver(post_save, sender=CourseAccessRole) def refresh_course_access_role_cache_on_save( @@ -90,3 +93,28 @@ def refresh_tenant_info_cache_on_delete_template_asset( template_tenant_id = get_all_tenants_info()['template_tenant']['tenant_id'] if template_tenant_id and instance.tenant_id == template_tenant_id: invalidate_cache() + + +@receiver(pre_save, sender=UserProfile) +def fix_profile_gender_issues( + sender: Any, instance: UserProfile, **kwargs: Any, # pylint: disable=unused-argument +) -> None: + """Receiver to fix profile.gender values when a user profile is saved""" + values_map = { + 'male': 'm', + 'female': 'f', + } + if instance.gender not in ['m', 'f', '']: + user_id_str = f'id: {str(instance.user_id)}' if instance.user_id else f'username: {instance.username}' + try: + original_value = instance.gender + new_value = values_map.get(original_value.lower(), '') + instance.gender = new_value + logging.warning( + 'UserProfile.gender updated for user %s from "%s" to "%s"', + user_id_str, + original_value, + new_value, + ) + except Exception as exc: + logger.error('Error updating gender for user %s: %s', user_id_str, str(exc)) diff --git a/tests/test_helpers/test_signals.py b/tests/test_helpers/test_signals.py index d90fe1d0..eb7ce99a 100644 --- a/tests/test_helpers/test_signals.py +++ b/tests/test_helpers/test_signals.py @@ -1,8 +1,10 @@ """Tests for the signals module of the helpers app""" + +import logging from unittest.mock import patch import pytest -from common.djangoapps.student.models import CourseAccessRole +from common.djangoapps.student.models import CourseAccessRole, UserProfile from django.core.cache import cache from futurex_openedx_extensions.helpers import constants as cs @@ -166,3 +168,48 @@ def test_refresh_tenant_info_cache_on_delete_template_asset( mock_invalidate.assert_called_once() else: mock_invalidate.assert_not_called() + + +@pytest.mark.django_db +@pytest.mark.parametrize( + 'original, expected, expect_warning', + [ + ('male', 'm', True), + ('Male', 'm', True), + ('female', 'f', True), + ('FEMALE', 'f', True), + ('m', 'm', False), + ('f', 'f', False), + ('', '', False), + ('M', '', True), + ('other', '', True), + ], +) +def test_fix_profile_gender_issues_on_create(original, expected, expect_warning, caplog): + """Verify that creating a UserProfile normalizes gender and logs when changed.""" + caplog.set_level(logging.WARNING) + user_id = 10 + profile = UserProfile.objects.create(user_id=user_id, gender=original) + profile.refresh_from_db() + assert profile.gender == expected + + if expect_warning: + assert any( + (str(original) in rec.getMessage() and str(expected) in rec.getMessage()) + for rec in caplog.records + ) + else: + assert not any('UserProfile.gender updated for user' in rec.getMessage() for rec in caplog.records) + + +@pytest.mark.django_db +def test_fix_profile_gender_issues_on_update(caplog): + """Verify that updating an existing UserProfile triggers normalization on save.""" + caplog.set_level(logging.WARNING) + user_id = 11 + profile = UserProfile.objects.create(user_id=user_id, gender='') + profile.gender = 'Male' + profile.save() + profile.refresh_from_db() + assert profile.gender == 'm' + assert any('UserProfile.gender updated for user' in rec.getMessage() for rec in caplog.records)