From c3e84157e7d7efe5fa183928ae44e753b73f8a0e Mon Sep 17 00:00:00 2001 From: dzhuang <dzhuang.scut@gmail.com> Date: Wed, 28 Feb 2018 18:26:34 +0800 Subject: [PATCH] For email appelation full_name to skip None first/last name. --- accounts/models.py | 31 +--- accounts/utils.py | 219 +++++++++++++++++++---------- course/constants.py | 2 + relate/checks.py | 18 +-- tests/test_accounts/test_models.py | 87 +++++++++++- tests/test_checks.py | 9 +- 6 files changed, 244 insertions(+), 122 deletions(-) diff --git a/accounts/models.py b/accounts/models.py index 85909e95..cf07b8a0 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -151,7 +151,7 @@ class User(AbstractBaseUser, PermissionsMixin): verbose_blank(first_name), verbose_blank(last_name)) from accounts.utils import relate_user_method_settings - format_method = relate_user_method_settings.get_custom_full_name_method + format_method = relate_user_method_settings.custom_full_name_method if format_method is None: format_method = default_fullname @@ -186,33 +186,14 @@ class User(AbstractBaseUser, PermissionsMixin): def get_email_appellation(self): "Return the appellation of the receiver in email." - from django.conf import settings - - # import the user defined priority list - customized_priority_list = getattr( - settings, - "RELATE_EMAIL_APPELATION_PRIORITY_LIST", []) - - # When the settings explicitly set it to None - if not customized_priority_list: - customized_priority_list = [] - priority_list = [] - - # filter out not allowd appellations in customized list - for e in customized_priority_list: - if e in ["first_name", "email", "username", "full_name"]: - priority_list.append(e) - - # make sure the default appellations are included in case - # user defined appellations are not available. - for e in ["first_name", "email", "username"]: - if e not in priority_list: - priority_list.append(e) + from accounts.utils import relate_user_method_settings + priority_list = ( + relate_user_method_settings.email_appelation_priority_list) for attr in priority_list: if attr == "full_name": - appellation = self.get_full_name(allow_blank=True) + appellation = self.get_full_name(allow_blank=False) else: appellation = getattr(self, attr) @@ -221,8 +202,6 @@ class User(AbstractBaseUser, PermissionsMixin): else: continue - return _("user") - def clean(self): super(User, self).clean() diff --git a/accounts/utils.py b/accounts/utils.py index 6100f072..77f73e26 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -29,7 +29,11 @@ from django.core.checks import Warning from django.utils.functional import cached_property from django.utils.module_loading import import_string +from relate.checks import INSTANCE_ERROR_PATTERN +from course.constants import DEFAULT_EMAIL_APPELATION_PRIORITY_LIST + RELATE_USER_FULL_NAME_FORMAT_METHOD = "RELATE_USER_FULL_NAME_FORMAT_METHOD" +RELATE_EMAIL_APPELATION_PRIORITY_LIST = "RELATE_EMAIL_APPELATION_PRIORITY_LIST" class RelateUserMethodSettingsInitializer(object): @@ -40,109 +44,172 @@ class RelateUserMethodSettingsInitializer(object): def __init__(self): self._custom_full_name_method = None + self._email_appelation_priority_list = ( + DEFAULT_EMAIL_APPELATION_PRIORITY_LIST) @cached_property - def get_custom_full_name_method(self): + def custom_full_name_method(self): self.check_custom_full_name_method() return self._custom_full_name_method + @cached_property + def email_appelation_priority_list(self): + self.check_email_appelation_priority_list() + return self._email_appelation_priority_list + + def check_email_appelation_priority_list(self): + errors = [] + self._email_appelation_priority_list = ( + DEFAULT_EMAIL_APPELATION_PRIORITY_LIST) + + from django.conf import settings + custom_email_appelation_priority_list = getattr( + settings, RELATE_EMAIL_APPELATION_PRIORITY_LIST, None) + if not custom_email_appelation_priority_list: + return errors + + if not isinstance(custom_email_appelation_priority_list, (list, tuple)): + errors.append(Warning( + msg=("%s, %s" % ( + INSTANCE_ERROR_PATTERN + % {"location": RELATE_EMAIL_APPELATION_PRIORITY_LIST, + "types": "list or tuple"}, + "default value '%s' will be used" + % repr(DEFAULT_EMAIL_APPELATION_PRIORITY_LIST))), + id="relate_email_appelation_priority_list.W001")) + return errors + + priority_list = [] + not_supported_appels = [] + + # filter out not allowd appellations in customized list + for appel in custom_email_appelation_priority_list: + if appel in DEFAULT_EMAIL_APPELATION_PRIORITY_LIST: + priority_list.append(appel) + else: + not_supported_appels.append(appel) + + # make sure the default appellations are included in case + # user defined appellations are not available. + for appel in DEFAULT_EMAIL_APPELATION_PRIORITY_LIST: + if appel not in priority_list: + priority_list.append(appel) + + assert len(priority_list) + self._email_appelation_priority_list = priority_list + + if not_supported_appels: + errors.append(Warning( + msg=("%(location)s: not supported email appelation(s) found " + "and will be ignored: %(not_supported_appelds)s. " + "%(actual)s will be used as " + "RELATE_EMAIL_APPELATION_PRIORITY_LIST." + % {"location": RELATE_EMAIL_APPELATION_PRIORITY_LIST, + "not_supported_appelds": ", ".join(not_supported_appels), + "actual": repr(priority_list)}), + id="relate_email_appelation_priority_list.W002")) + return errors + def check_custom_full_name_method(self): + self._custom_full_name_method = None errors = [] from django.conf import settings relate_user_full_name_format_method = getattr( settings, RELATE_USER_FULL_NAME_FORMAT_METHOD, None) - self._custom_full_name_method = None - if relate_user_full_name_format_method is not None: - if isinstance(relate_user_full_name_format_method, six.string_types): - try: - relate_user_full_name_format_method = ( - import_string(relate_user_full_name_format_method)) - except ImportError: - errors = [Warning( - msg=( - "%(location)s: `%(method)s` failed to be imported, " - "default format method will be used." - % {"location": RELATE_USER_FULL_NAME_FORMAT_METHOD, - "method": relate_user_full_name_format_method - } - ), - id="relate_user_full_name_format_method.W001" - )] - return errors - - self._custom_full_name_method = relate_user_full_name_format_method - if not callable(relate_user_full_name_format_method): - errors.append(Warning( + + if relate_user_full_name_format_method is None: + return errors + + if isinstance(relate_user_full_name_format_method, six.string_types): + try: + relate_user_full_name_format_method = ( + import_string(relate_user_full_name_format_method)) + except ImportError: + errors = [Warning( msg=( - "%(location)s: `%(method)s` is not a callable, " + "%(location)s: `%(method)s` failed to be imported, " "default format method will be used." % {"location": RELATE_USER_FULL_NAME_FORMAT_METHOD, "method": relate_user_full_name_format_method } ), - id="relate_user_full_name_format_method.W002" + id="relate_user_full_name_format_method.W001" + )] + return errors + + self._custom_full_name_method = relate_user_full_name_format_method + if not callable(relate_user_full_name_format_method): + errors.append(Warning( + msg=( + "%(location)s: `%(method)s` is not a callable, " + "default format method will be used." + % {"location": RELATE_USER_FULL_NAME_FORMAT_METHOD, + "method": relate_user_full_name_format_method + } + ), + id="relate_user_full_name_format_method.W002" + )) + else: + try: + returned_name = ( + relate_user_full_name_format_method("first_name", + "last_name")) + except Exception as e: + from traceback import format_exc + errors.append(Warning( + msg=( + "%(location)s: `%(method)s` called with '" + "args 'first_name', 'last_name' failed with" + "exception below:\n" + "%(err_type)s: %(err_str)s\n" + "%(format_exc)s\n\n" + "Default format method will be used." + % {"location": RELATE_USER_FULL_NAME_FORMAT_METHOD, + "method": relate_user_full_name_format_method, + "err_type": type(e).__name__, + "err_str": str(e), + 'format_exc': format_exc()} + ), + id="relate_user_full_name_format_method.W003" )) else: - try: - returned_name = ( - relate_user_full_name_format_method("first_name", - "last_name")) - except Exception as e: - from traceback import format_exc + unexpected_return_value = "" + if returned_name is None: + unexpected_return_value = "None" + if not isinstance(returned_name, six.string_types): + unexpected_return_value = type(returned_name).__name__ + elif not returned_name.strip(): + unexpected_return_value = "empty string %s" % returned_name + if unexpected_return_value: errors.append(Warning( - msg=( - "%(location)s: `%(method)s` called with '" - "args 'first_name', 'last_name' failed with" - "exception below:\n" - "%(err_type)s: %(err_str)s\n" - "%(format_exc)s\n\n" - "Default format method will be used." - % {"location": RELATE_USER_FULL_NAME_FORMAT_METHOD, - "method": relate_user_full_name_format_method, - "err_type": type(e).__name__, - "err_str": str(e), - 'format_exc': format_exc()} - ), - id="relate_user_full_name_format_method.W003" + msg=("%(location)s: `%(method)s` is expected to " + "return a non-empty string, got `%(result)s`, " + "default format method will be used." + % { + "location": RELATE_USER_FULL_NAME_FORMAT_METHOD, + "method": relate_user_full_name_format_method, + "result": unexpected_return_value, + }), + id="relate_user_full_name_format_method.W004" )) else: - unexpected_return_value = "" - if returned_name is None: - unexpected_return_value = "None" - if not isinstance(returned_name, six.string_types): - unexpected_return_value = type(returned_name).__name__ - elif not returned_name.strip(): - unexpected_return_value = "empty string %s" % returned_name - if unexpected_return_value: + returned_name2 = ( + relate_user_full_name_format_method("first_name2", + "last_name2")) + if returned_name == returned_name2: errors.append(Warning( msg=("%(location)s: `%(method)s` is expected to " - "return a non-empty string, got `%(result)s`, " - "default format method will be used." + "return different value with different " + "input, default format method will be used." % { - "location": RELATE_USER_FULL_NAME_FORMAT_METHOD, - "method": relate_user_full_name_format_method, - "result": unexpected_return_value, + "location": + RELATE_USER_FULL_NAME_FORMAT_METHOD, + "method": + relate_user_full_name_format_method }), - id="relate_user_full_name_format_method.W004" + id="relate_user_full_name_format_method.W005" )) - else: - returned_name2 = ( - relate_user_full_name_format_method("first_name2", - "last_name2")) - if returned_name == returned_name2: - errors.append(Warning( - msg=("%(location)s: `%(method)s` is expected to " - "return different value with different " - "input, default format method will be used." - % { - "location": - RELATE_USER_FULL_NAME_FORMAT_METHOD, - "method": - relate_user_full_name_format_method - }), - id="relate_user_full_name_format_method.W005" - )) if errors: self._custom_full_name_method = None diff --git a/course/constants.py b/course/constants.py index e4207e42..9b53876e 100644 --- a/course/constants.py +++ b/course/constants.py @@ -30,6 +30,8 @@ if False: from django.utils.translation import pgettext_lazy, ugettext # Allow 10x extra credit at the very most. MAX_EXTRA_CREDIT_FACTOR = 10 +DEFAULT_EMAIL_APPELATION_PRIORITY_LIST = [ + "first_name", "email", "username", "full_name"] COURSE_ID_REGEX = "(?P<course_identifier>[-a-zA-Z0-9]+)" diff --git a/relate/checks.py b/relate/checks.py index 4e76c63a..c18662ec 100644 --- a/relate/checks.py +++ b/relate/checks.py @@ -101,22 +101,12 @@ def check_relate_settings(app_configs, **kwargs): )) # }}} - # {{{ check RELATE_EMAIL_APPELATION_PRIORITY_LIST - relate_email_appelation_priority_list = getattr( - settings, RELATE_EMAIL_APPELATION_PRIORITY_LIST, None) - if relate_email_appelation_priority_list is not None: - if not isinstance(relate_email_appelation_priority_list, (list, tuple)): - errors.append(RelateCriticalCheckMessage( - msg=( - INSTANCE_ERROR_PATTERN - % {"location": RELATE_EMAIL_APPELATION_PRIORITY_LIST, - "types": "list or tuple"}), - id="relate_email_appelation_priority_list.E001") - ) - # }}} + from accounts.utils import relate_user_method_settings + # check RELATE_EMAIL_APPELATION_PRIORITY_LIST + errors.extend( + relate_user_method_settings.check_email_appelation_priority_list()) # check RELATE_CSV_SETTINGS - from accounts.utils import relate_user_method_settings errors.extend(relate_user_method_settings.check_custom_full_name_method()) # {{{ check EMAIL_CONNECTIONS diff --git a/tests/test_accounts/test_models.py b/tests/test_accounts/test_models.py index 09066fd1..761b2173 100644 --- a/tests/test_accounts/test_models.py +++ b/tests/test_accounts/test_models.py @@ -84,7 +84,7 @@ class UserModelTest(TestCase): "tests.resource.my_customized_get_full_name_method") get_custom_full_name_method_path = ( "accounts.utils.RelateUserMethodSettingsInitializer" - ".get_custom_full_name_method") + ".custom_full_name_method") with override_settings( RELATE_USER_FULL_NAME_FORMAT_METHOD=custom_get_full_name_path): @@ -109,13 +109,13 @@ class UserModelTest(TestCase): def test_custom_get_full_name_method_is_cached(self): """ - Test relate_user_method_settings.get_custom_full_name_method is cached. + Test relate_user_method_settings.custom_full_name_method is cached. """ user = UserFactory.create(first_name="my_first", last_name="my_last") custom_get_full_name_path = ( "tests.resource.my_customized_get_full_name_method") - get_custom_full_name_check_path = ( + custom_full_name_check_path = ( "accounts.utils.RelateUserMethodSettingsInitializer" ".check_custom_full_name_method") @@ -128,6 +128,85 @@ class UserModelTest(TestCase): user.get_full_name() - with mock.patch(get_custom_full_name_check_path) as mock_check: + with mock.patch(custom_full_name_check_path) as mock_check: user.get_full_name() self.assertEqual(mock_check.call_count, 0) + + def test_get_email_appellation_priority_list(self): + user = UserFactory.create(first_name="my_first", last_name="my_last") + + from accounts.utils import relate_user_method_settings + relate_user_method_settings.__dict__ = {} + + with override_settings( + RELATE_USER_FULL_NAME_FORMAT_METHOD=None): + self.assertEqual(user.get_email_appellation(), "my_first") + + relate_user_method_settings.__dict__ = {} + + with override_settings( + RELATE_EMAIL_APPELATION_PRIORITY_LIST=[""]): + relate_user_method_settings.__dict__ = {} + self.assertEqual(user.get_email_appellation(), "my_first") + + relate_user_method_settings.__dict__ = {} + + with override_settings( + RELATE_EMAIL_APPELATION_PRIORITY_LIST=["whatever"]): + self.assertEqual(user.get_email_appellation(), "my_first") + + relate_user_method_settings.__dict__ = {} + + # not a list + with override_settings( + RELATE_EMAIL_APPELATION_PRIORITY_LIST="whatever"): + self.assertEqual(user.get_email_appellation(), "my_first") + + relate_user_method_settings.__dict__ = {} + + with override_settings( + RELATE_EMAIL_APPELATION_PRIORITY_LIST=["full_name"], + RELATE_USER_FULL_NAME_FORMAT_METHOD=None): + self.assertEqual(user.get_email_appellation(), "my_first my_last") + + # create a user without first_name + user = UserFactory.create(last_name="my_last") + + relate_user_method_settings.__dict__ = {} + + # the next appelation is email + with override_settings( + RELATE_USER_FULL_NAME_FORMAT_METHOD=None): + self.assertEqual(user.get_email_appellation(), user.email) + + relate_user_method_settings.__dict__ = {} + + with override_settings( + RELATE_EMAIL_APPELATION_PRIORITY_LIST=["full_name", "username"], + RELATE_USER_FULL_NAME_FORMAT_METHOD=None): + # because full_name is None + self.assertEqual(user.get_email_appellation(), user.username) + + def test_get_email_appellation_priority_list_is_cached(self): + """ + Test relate_user_method_settings.email_appelation_priority_list is cached. + """ + user = UserFactory.create(first_name="my_first", last_name="my_last") + + email_appel_priority_list_check_path = ( + "accounts.utils.RelateUserMethodSettingsInitializer" + ".check_email_appelation_priority_list") + + from accounts.utils import relate_user_method_settings + relate_user_method_settings.__dict__ = {} + + with override_settings( + RELATE_EMAIL_APPELATION_PRIORITY_LIST=["full_name", "username"], + RELATE_USER_FULL_NAME_FORMAT_METHOD=None): + self.assertEqual(user.get_email_appellation(), "my_first my_last") + + user2 = UserFactory.create(first_name="my_first") + with mock.patch(email_appel_priority_list_check_path) as mock_check: + self.assertEqual(user2.get_email_appellation(), + user2.username) + self.assertEqual(mock_check.call_count, 0) diff --git a/tests/test_checks.py b/tests/test_checks.py index 338af0ea..c67bfe52 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -291,8 +291,9 @@ class CheckRelateEmailAppelationPriorityList(CheckRelateSettingsBase): msg_id_prefix = "relate_email_appelation_priority_list" VALID_CONF_NONE = None - VALID_CONF = ["name1", "name2"] + VALID_CONF = ["full_name"] INVALID_CONF_STR = "name1" + INVALID_CONF = ["name1", "name2"] @override_settings(RELATE_EMAIL_APPELATION_PRIORITY_LIST=VALID_CONF_NONE) def test_valid_relate_email_appelation_priority_list_none(self): @@ -305,7 +306,11 @@ class CheckRelateEmailAppelationPriorityList(CheckRelateSettingsBase): @override_settings(RELATE_EMAIL_APPELATION_PRIORITY_LIST=INVALID_CONF_STR) def test_invalid_relate_email_appelation_priority_list_str(self): self.assertCheckMessages( - ["relate_email_appelation_priority_list.E001"]) + ["relate_email_appelation_priority_list.W001"]) + + @override_settings(RELATE_EMAIL_APPELATION_PRIORITY_LIST=INVALID_CONF) + def test_valid_relate_email_appelation_priority_list_invalid(self): + self.assertCheckMessages(["relate_email_appelation_priority_list.W002"]) class CheckRelateEmailConnections(CheckRelateSettingsBase): -- GitLab