diff --git a/accounts/models.py b/accounts/models.py
index e09b6c5517fc259e237798aec76c439ff42b8b68..92a6e259130206c29cca2f6a433b45281d5324cc 100644
--- a/accounts/models.py
+++ b/accounts/models.py
@@ -172,13 +172,21 @@ class User(AbstractBaseUser, PermissionsMixin):
         def default_mask_method(user):
             return "%s%s" % (_("User"), str(user.pk))
 
-        from django.conf import settings
-        mask_method = getattr(
-                settings,
-                "RELATE_USER_PROFILE_MASK_METHOD",
-                default_mask_method)
-
-        return str(mask_method(self)).strip()
+        from accounts.utils import relate_user_method_settings
+        mask_method = relate_user_method_settings.custom_profile_mask_method
+        if mask_method is None:
+            mask_method = default_mask_method
+
+        # Intentionally don't fallback if it failed -- let user see the exception.
+        result = mask_method(self)
+        if not result:
+            raise RuntimeError("get_masked_profile should not None.")
+        else:
+            result = str(result).strip()
+        if not result:
+            raise RuntimeError("get_masked_profile should not return "
+                               "an empty string.")
+        return result
 
     def get_short_name(self):
         "Returns the short name for the user."
diff --git a/accounts/utils.py b/accounts/utils.py
index b26662fda8dc62b68c83f30544d8113d887faed9..ddf8a29bb893d4f09e9a055ebc6333e1f4f41443 100644
--- a/accounts/utils.py
+++ b/accounts/utils.py
@@ -25,16 +25,17 @@ THE SOFTWARE.
 """
 
 import six
-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 relate.checks import (
+    INSTANCE_ERROR_PATTERN, Warning, RelateCriticalCheckMessage)
 from course.constants import DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST
 
 RELATE_USER_FULL_NAME_FORMAT_METHOD = "RELATE_USER_FULL_NAME_FORMAT_METHOD"
 RELATE_EMAIL_APPELLATION_PRIORITY_LIST = (
     "RELATE_EMAIL_APPELLATION_PRIORITY_LIST")
+RELATE_USER_PROFILE_MASK_METHOD = "RELATE_USER_PROFILE_MASK_METHOD"
 
 
 class RelateUserMethodSettingsInitializer(object):
@@ -47,6 +48,7 @@ class RelateUserMethodSettingsInitializer(object):
         self._custom_full_name_method = None
         self._email_appellation_priority_list = (
                 DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST)
+        self._custom_profile_mask_method = None
 
     @cached_property
     def custom_full_name_method(self):
@@ -58,6 +60,80 @@ class RelateUserMethodSettingsInitializer(object):
         self.check_email_appellation_priority_list()
         return self._email_appellation_priority_list
 
+    @cached_property
+    def custom_profile_mask_method(self):
+        self.check_user_profile_mask_method()
+        return self._custom_profile_mask_method
+
+    def check_user_profile_mask_method(self):
+        self._custom_profile_mask_method = None
+        errors = []
+
+        from django.conf import settings
+        custom_user_profile_mask_method = getattr(
+            settings, RELATE_USER_PROFILE_MASK_METHOD, None)
+
+        if custom_user_profile_mask_method is None:
+            return errors
+
+        if isinstance(custom_user_profile_mask_method, six.string_types):
+            try:
+                custom_user_profile_mask_method = (
+                    import_string(custom_user_profile_mask_method))
+            except ImportError:
+                errors = [RelateCriticalCheckMessage(
+                    msg=(
+                            "%(location)s: `%(method)s` failed to be imported. "
+                            % {"location": RELATE_USER_PROFILE_MASK_METHOD,
+                               "method": custom_user_profile_mask_method
+                               }
+                    ),
+                    id="relate_user_profile_mask_method.E001"
+                )]
+                return errors
+
+        self._custom_profile_mask_method = custom_user_profile_mask_method
+        if not callable(custom_user_profile_mask_method):
+            errors.append(RelateCriticalCheckMessage(
+                msg=(
+                        "%(location)s: `%(method)s` is not a callable. "
+                        % {"location": RELATE_USER_PROFILE_MASK_METHOD,
+                           "method": custom_user_profile_mask_method
+                           }
+                ),
+                id="relate_user_profile_mask_method.E002"
+            ))
+        else:
+            import inspect
+            if six.PY3:
+                sig = inspect.signature(custom_user_profile_mask_method)
+                n_args = len([p.name for p in sig.parameters.values()])
+            else:
+                # Don't count the number of defaults.
+                # (getargspec returns args, varargs, varkw, defaults)
+                n_args = sum(
+                    [len(arg) for arg
+                     in inspect.getargspec(custom_user_profile_mask_method)[:3]
+                     if arg is not None])
+
+            if not n_args or n_args > 1:
+                errors.append(RelateCriticalCheckMessage(
+                    msg=(
+                        "%(location)s: `%(method)s` should have exactly "
+                        "one arg, got %(n)d."
+                        % {"location": RELATE_USER_PROFILE_MASK_METHOD,
+                           "method": custom_user_profile_mask_method,
+                           "n": n_args
+                           }
+                    ),
+                    id="relate_user_profile_mask_method.E003"
+                ))
+
+        if errors:
+            self._custom_profile_mask_method = None
+
+        return errors
+
     def check_email_appellation_priority_list(self):
         errors = []
         self._email_appellation_priority_list = (
diff --git a/accounts/views.py b/accounts/views.py
index 6100593bc427cadce19dee2f7eb0c7f1473a8ad9..0e824d63b7d0b94c018412f4d472f0fd137c3f57 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,3 +1,3 @@
-from django.shortcuts import render  # noqa
+# from django.shortcuts import render  # noqa
 
 # Create your views here.
diff --git a/local_settings_example.py b/local_settings_example.py
index 9d0b40ba04c0094bb7734fc4856c3609cf7c06da..2cf368621da94ec046fac6e0b7240abefd40ad02 100644
--- a/local_settings_example.py
+++ b/local_settings_example.py
@@ -224,7 +224,7 @@ RELATE_SHOW_EDITOR_FORM = True
 # For example, you can define it like this:
 
 #<code>
-#   def my_fullname_format(firstname, lastname)
+#   def my_fullname_format(firstname, lastname):
 #         return "%s%s" % (last_name, first_name)
 #</code>
 
@@ -234,7 +234,7 @@ RELATE_SHOW_EDITOR_FORM = True
 
 # You can also import it from your custom module, or use a dotted path of the
 # method, i.e.:
-# RELATE_USER_FULL_NAME_FORMAT_METHOD = "path.to.my_fullname_format"
+#RELATE_USER_FULL_NAME_FORMAT_METHOD = "path.to.my_fullname_format"
 
 # }}}
 
@@ -256,6 +256,31 @@ RELATE_SHOW_EDITOR_FORM = True
 
 # }}}
 
+# {{{ custom method for masking user profile
+# When a participation, for example, teaching assistant, has limited access to
+# students' profile (i.e., has_permission(pperm.view_participant_masked_profile)),
+# a built-in mask method (which is based on pk of user instances) is used be
+# default. The mask method can be overriden by the following a custom method, with
+# user as the args.
+
+#RELATE_USER_PROFILE_MASK_METHOD = "path.tomy_method
+# For example, you can define it like this:
+
+#<code>
+#   def my_mask_method(user):
+#         return "User_%s" % str(user.pk + 100)
+#</code>
+
+# and then uncomment the following line and enable it with:
+
+#RELATE_USER_PROFILE_MASK_METHOD = my_mask_method
+
+# You can also import it from your custom module, or use a dotted path of the
+# method, i.e.:
+#RELATE_USER_PROFILE_MASK_METHOD = "path.to.my_mask_method"
+
+# }}}
+
 # {{{ extra checks
 
 # This allow user to add customized startup checkes for user-defined modules
diff --git a/relate/checks.py b/relate/checks.py
index b4761fe0516962b6408a23161690063c9321eb02..afff40b942b1956377c9533efd8b704be67268bb 100644
--- a/relate/checks.py
+++ b/relate/checks.py
@@ -108,6 +108,9 @@ def check_relate_settings(app_configs, **kwargs):
     # check RELATE_CSV_SETTINGS
     errors.extend(relate_user_method_settings.check_custom_full_name_method())
 
+    # check RELATE_USER_PROFILE_MASK_METHOD
+    errors.extend(relate_user_method_settings.check_user_profile_mask_method())
+
     # {{{ check EMAIL_CONNECTIONS
     email_connections = getattr(settings, EMAIL_CONNECTIONS, None)
     if email_connections is not None:
diff --git a/tests/resource.py b/tests/resource.py
index 0985a7ef7719b2d3eb4cca68f66528df212b4610..3f78438b5a2d60d2fa80bbcac5d3bb171e8cc67f 100644
--- a/tests/resource.py
+++ b/tests/resource.py
@@ -27,8 +27,23 @@ def my_customized_get_full_name_method(first_name, last_name):
     return "%s %s" % (first_name.title(), last_name.title())
 
 
-def my_customized_get_full_name_method_invalid(first_name, last_name):  # noqa
+def my_customized_get_full_name_method_invalid(first_name, last_name):
     return None
 
 
 my_customized_get_full_name_method_invalid_str = "some_string"
+
+
+def my_custom_get_masked_profile_method_valid(u):
+    return "%s%s" % ("User", str(u.pk + 100))
+
+
+my_custom_get_masked_profile_method_invalid_str = "some_string"
+
+
+def my_custom_get_masked_profile_method_valid_but_return_none(u):
+    return
+
+
+def my_custom_get_masked_profile_method_valid_but_return_emtpy_string(u):
+    return "  "
diff --git a/tests/test_accounts/test_models.py b/tests/test_accounts/test_models.py
index 2c6a4ef3b9bb04ab7254c27b5d767dfd5762b0a8..6ec84f534684269e576c047d2bc17d4bc319857c 100644
--- a/tests/test_accounts/test_models.py
+++ b/tests/test_accounts/test_models.py
@@ -252,3 +252,26 @@ class UserModelTest(TestCase):
                 RELATE_EMAIL_APPELLATION_PRIORITY_LIST=["full_name"],
                 RELATE_USER_FULL_NAME_FORMAT_METHOD=None):
             self.assertEqual(user.get_email_appellation(), "my_first my_last")
+
+    def test_user_profile_mask_method_is_cached(self):
+        user = UserFactory.create(first_name="my_first", last_name="my_last")
+
+        from accounts.utils import relate_user_method_settings
+
+        user_profile_mask_method_check_path = (
+            "accounts.utils.RelateUserMethodSettingsInitializer"
+            ".check_user_profile_mask_method")
+
+        def custom_method(u):
+            return "%s%s" % ("User", str(u.pk + 100))
+
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD=custom_method):
+            relate_user_method_settings.__dict__ = {}
+            self.assertEqual(user.get_masked_profile(), custom_method(user))
+
+            user2 = UserFactory.create(first_name="my_first")
+
+            with mock.patch(user_profile_mask_method_check_path) as mock_check:
+                self.assertEqual(user2.get_masked_profile(),
+                                 custom_method(user2))
+                self.assertEqual(mock_check.call_count, 0)
diff --git a/tests/test_checks.py b/tests/test_checks.py
index f298233d1fe993d510bc66e5adbbf4ca5b4d8e6f..3749762cb577738bed49999e315a45e48d2d8200 100644
--- a/tests/test_checks.py
+++ b/tests/test_checks.py
@@ -130,6 +130,135 @@ class CheckRelateURL(CheckRelateSettingsBase):
         self.assertCheckMessages(["relate_base_url.E003"])
 
 
+class CheckRelateUserProfileMaskMethod(CheckRelateSettingsBase):
+    # This TestCase is not pure for check, but also make sure it returned
+    # expected result
+    allow_database_queries = True
+
+    msg_id_prefix = "relate_user_profile_mask_method"
+
+    def setUp(self):
+        super(CheckRelateUserProfileMaskMethod, self).setUp()
+        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__ = {}
+
+    def test_get_masked_profile_not_configured(self):
+        with override_settings():
+            del settings.RELATE_USER_PROFILE_MASK_METHOD
+            self.assertCheckMessages([])
+
+            # make sure it runs without issue
+            self.assertIsNotNone(self.user.get_masked_profile())
+
+    def test_get_masked_profile_valid_none(self):
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD=None):
+            self.assertCheckMessages([])
+
+            # make sure it runs without issue
+            self.assertIsNotNone(self.user.get_masked_profile())
+
+    def test_get_masked_profile_valid_method1(self):
+        def custom_method(u):
+            return "%s%s" % ("User", str(u.pk + 1))
+
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD=custom_method):
+            self.assertCheckMessages([])
+            self.assertEqual(self.user.get_masked_profile(),
+                             custom_method(self.user))
+
+    def test_get_masked_profile_valid_method2(self):
+        def custom_method(user=None):
+            if user is not None:
+                return "%s%s" % ("User", str(user.pk + 1))
+            else:
+                return ""
+
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD=custom_method):
+            self.assertCheckMessages([])
+            self.assertEqual(self.user.get_masked_profile(),
+                             custom_method(self.user))
+
+    def test_get_masked_profile_valid_method_path(self):
+        with override_settings(
+                RELATE_USER_PROFILE_MASK_METHOD=(
+                        "tests.resource"
+                        ".my_custom_get_masked_profile_method_valid")):
+            self.assertCheckMessages([])
+            from tests.resource import (
+                my_custom_get_masked_profile_method_valid as custom_method)
+            self.assertEqual(self.user.get_masked_profile(),
+                             custom_method(self.user))
+
+    def test_get_masked_profile_param_invalid1(self):
+        # the method has 0 args/kwargs
+        def custom_method():
+            return "profile"
+
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD=custom_method):
+            self.assertCheckMessages(['relate_user_profile_mask_method.E003'])
+
+    def test_get_masked_profile_param_invalid2(self):
+        # the method has 2 args/kwargs
+        def custom_method(u, v):
+            return "%s%s" % ("User", str(u.pk + 1))
+
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD=custom_method):
+            self.assertCheckMessages(['relate_user_profile_mask_method.E003'])
+
+    def test_get_masked_profile_param_invalid3(self):
+        # the method has 2 args/kwargs
+        def custom_method(u, v=None):
+            return "%s%s" % ("User", str(u.pk + 1))
+
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD=custom_method):
+            self.assertCheckMessages(['relate_user_profile_mask_method.E003'])
+
+    def test_get_masked_profile_invalid_path(self):
+        with override_settings(RELATE_USER_PROFILE_MASK_METHOD="invalid path"):
+            self.assertCheckMessages(['relate_user_profile_mask_method.E001'])
+
+    def test_get_masked_profile_valid_path_not_callable(self):
+        with override_settings(
+                RELATE_USER_PROFILE_MASK_METHOD=(
+                        "tests.resource"
+                        ".my_custom_get_masked_profile_method_invalid_str")):
+            self.assertCheckMessages(['relate_user_profile_mask_method.E002'])
+
+    def test_passed_check_but_return_none(self):
+        with override_settings(
+                RELATE_USER_PROFILE_MASK_METHOD=(
+                        "tests.resource"
+                        ".my_custom_get_masked_profile_method_valid_but_return_none")):  # noqa
+            self.assertCheckMessages([])
+            from tests.resource import (
+                my_custom_get_masked_profile_method_valid_but_return_none
+                as custom_method)
+
+            # test method can run
+            custom_method(self.user)
+
+            with self.assertRaises(RuntimeError):
+                self.user.get_masked_profile()
+
+    def test_passed_check_but_return_empty_string(self):
+        with override_settings(
+                RELATE_USER_PROFILE_MASK_METHOD=(
+                        "tests.resource"
+                        ".my_custom_get_masked_profile_method_valid_but_return_emtpy_string")):  # noqa
+            self.assertCheckMessages([])
+            from tests.resource import (
+                my_custom_get_masked_profile_method_valid_but_return_emtpy_string
+                as custom_method)
+
+            # test method can run
+            custom_method(self.user)
+
+            with self.assertRaises(RuntimeError):
+                self.user.get_masked_profile()
+
+
 class CheckRelateUserFullNameFormatMethod(CheckRelateSettingsBase):
     # This TestCase is not pure for check, but also make sure it returned
     # expected result
@@ -145,19 +274,19 @@ class CheckRelateUserFullNameFormatMethod(CheckRelateSettingsBase):
         def invalid_method1(first_name):
             return first_name
 
-        def invalid_method2(first_name, last_name):  # noqa
+        def invalid_method2(first_name, last_name):
             return None
 
-        def invalid_method3(first_name, last_name):  # noqa
+        def invalid_method3(first_name, last_name):
             return " "
 
-        def invalid_method4(first_name, last_name):  # noqa
+        def invalid_method4(first_name, last_name):
             return b"my_name"
 
-        def invalid_method5(first_name, last_name):  # noqa
+        def invalid_method5(first_name, last_name):
             return "my_name"
 
-        def invalid_method6(first_name, last_name):  # noqa
+        def invalid_method6(first_name, last_name):
             return Exception()
 
         default_user_dict = {"first_name": "first_name", "last_name": "last_name"}