diff --git a/accounts/models.py b/accounts/models.py index 51c97f38d66f1ee9290dbdbdaef9845a40ba0fbf..e0d8b6706c6f26cc8e392b91727cd7fc4c7da9f5 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -171,6 +171,22 @@ class User(AbstractBaseUser, PermissionsMixin): return full_name.strip() + def get_masked_profile(self): + """ + Returns the masked user profile. + """ + + 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() + def get_short_name(self): "Returns the short name for the user." return self.first_name diff --git a/course/auth.py b/course/auth.py index 84fe455bdd1eb9473e843c23e272502be16c809e..30da4277f6b426a50396ec853b50ebf08055da48 100644 --- a/course/auth.py +++ b/course/auth.py @@ -88,6 +88,12 @@ def may_impersonate(impersonator, impersonee): status=participation_status.active) for part in my_participations: + # FIXME: if a TA is not allowed to view participants' + # profile in one course, then he/she is not able to impersonate + # any user, even in courses he/she is allow to view profiles + # of all users. + if part.has_permission(pperm.view_participant_masked_profile): + return False impersonable_roles = [ argument for perm, argument in part.permissions() diff --git a/course/constants.py b/course/constants.py index 00cce6b490ae3759d9798e0543378398bfeac23b..331420238d94429468b14dff7a3512465d6be135 100644 --- a/course/constants.py +++ b/course/constants.py @@ -92,6 +92,7 @@ class participation_permission: # noqa issue_exam_ticket = "issue_exam_ticket" batch_issue_exam_ticket = "batch_issue_exam_ticket" + view_participant_masked_profile = "view_participant_masked_profile" view_flow_sessions_from_role = "view_flow_sessions_from_role" view_gradebook = "view_gradebook" edit_grading_opportunity = "edit_grading_opportunity" @@ -161,9 +162,12 @@ PARTICIPATION_PERMISSION_CHOICES = ( (participation_permission.batch_issue_exam_ticket, pgettext_lazy("Participation permission", "Batch issue exam ticket")), + (participation_permission.view_participant_masked_profile, + pgettext_lazy("Participation permission", + "View participants' masked profile only")), (participation_permission.view_flow_sessions_from_role, pgettext_lazy("Participation permission", - "View flow sessions from role ")), + "View flow sessions from role")), (participation_permission.view_gradebook, pgettext_lazy("Participation permission", "View gradebook")), (participation_permission.edit_grading_opportunity, diff --git a/course/flow.py b/course/flow.py index a00af1b3df8fa25d5fd9352929e00f7b7c2a2f35..761e10ab32edf38587a9c7b7b6c591aa7442b94e 100644 --- a/course/flow.py +++ b/course/flow.py @@ -2335,6 +2335,14 @@ def send_email_about_flow_page(pctx, flow_session_id, ordinal): from django.utils import translation with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE): from django.template.loader import render_to_string + from course.utils import will_use_masked_profile_for_email + use_masked_profile_for_email = ( + will_use_masked_profile_for_email(recipient_list) + ) + if use_masked_profile_for_email: + username = pctx.participation.user.get_masked_profile() + else: + username = pctx.participation.user.get_full_name() message = render_to_string( "course/flow-page-interaction-email.txt", { "page_id": page_id, @@ -2342,7 +2350,7 @@ def send_email_about_flow_page(pctx, flow_session_id, ordinal): "course": pctx.course, "question_text": form.cleaned_data["message"], "review_uri": review_uri, - "username": pctx.participation.user.get_full_name() + "username": username }) from django.core.mail import EmailMessage @@ -2354,7 +2362,7 @@ def send_email_about_flow_page(pctx, flow_session_id, ordinal): 'identifier': pctx.course_identifier, 'flow_id': flow_session_id, 'page_id': page_id, - 'username': pctx.participation.user.get_full_name() + 'username': username }, body=message, from_email=from_email, @@ -2570,6 +2578,11 @@ def finish_flow_session_view(pctx, flow_session_id): if (hasattr(fctx.flow_desc, "notify_on_submit") and fctx.flow_desc.notify_on_submit): + from course.utils import will_use_masked_profile_for_email + staff_email = ( + fctx.flow_desc.notify_on_submit + [fctx.course.notify_email]) + use_masked_profile = will_use_masked_profile_for_email(staff_email) + if (grading_rule.grade_identifier and flow_session.participation is not None): from course.models import get_flow_grading_opportunity @@ -2590,17 +2603,30 @@ def finish_flow_session_view(pctx, flow_session_id): with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE): from django.template.loader import render_to_string + participation = flow_session.participation message = render_to_string("course/submit-notify.txt", { "course": fctx.course, "flow_session": flow_session, + "use_masked_profile": use_masked_profile, "review_uri": pctx.request.build_absolute_uri(review_uri) }) + participation_desc = repr(participation) + if use_masked_profile: + participation_desc = _( + "%(user)s in %(course)s as %(role)s") % { + "user": participation.user.get_masked_profile(), + "course": flow_session.course, + "role": "/".join( + role.identifier + for role in participation.roles.all()) + } + from django.core.mail import EmailMessage msg = EmailMessage( string_concat("[%(identifier)s:%(flow_id)s] ", - _("Submission by %(participation)s")) - % {'participation': flow_session.participation, + _("Submission by %(participation_desc)s")) + % {'participation_desc': participation_desc, 'identifier': fctx.course.identifier, 'flow_id': flow_session.flow_id}, message, diff --git a/course/page/base.py b/course/page/base.py index 3704ba0500f499e5da90bff087249660506b46eb..a8259034145ed22cdea1c17516dd98bd8de25826 100644 --- a/course/page/base.py +++ b/course/page/base.py @@ -981,6 +981,8 @@ class PageBaseWithHumanTextFeedback(PageBase): if grading_form.cleaned_data["notify"] and page_context.flow_session: with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE): from django.template.loader import render_to_string + from course.utils import will_use_masked_profile_for_email + staff_email = [page_context.course.notify_email, request.user.email] message = render_to_string("course/grade-notify.txt", { "page_title": self.title(page_context, page_data), "course": page_context.course, @@ -988,6 +990,8 @@ class PageBaseWithHumanTextFeedback(PageBase): "feedback_text": grade_data["feedback_text"], "flow_session": page_context.flow_session, "review_uri": page_context.page_uri, + "use_masked_profile": + will_use_masked_profile_for_email(staff_email) }) from django.core.mail import EmailMessage @@ -1015,6 +1019,8 @@ class PageBaseWithHumanTextFeedback(PageBase): and page_context.flow_session): with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE): from django.template.loader import render_to_string + from course.utils import will_use_masked_profile_for_email + staff_email = [page_context.course.notify_email, request.user.email] message = render_to_string("course/grade-internal-notes-notify.txt", { "page_title": self.title(page_context, page_data), @@ -1023,7 +1029,9 @@ class PageBaseWithHumanTextFeedback(PageBase): "notes_text": grade_data["notes"], "flow_session": page_context.flow_session, "review_uri": page_context.page_uri, - "sender": request.user + "sender": request.user, + "use_masked_profile": + will_use_masked_profile_for_email(staff_email) }) from django.core.mail import EmailMessage diff --git a/course/templates/course/flow-page.html b/course/templates/course/flow-page.html index 1636ad64cf05c750d9fb11fe485d741962d6adb8..d1e625544911646232d0fffbcc908eaeb2525b59 100644 --- a/course/templates/course/flow-page.html +++ b/course/templates/course/flow-page.html @@ -21,7 +21,7 @@ {% trans "Viewing session for partipant" %} {% if flow_session.participation != None %} - {{ flow_session.participation.user.get_full_name }}. + {% if not pperm.view_participant_masked_profile %}{{ flow_session.participation.user.username }}{% else %}{{ flow_session.participation.user.get_masked_profile }}{% endif %}. {% else %} {% trans "(unspecified participant)" %}. {% endif %} diff --git a/course/templates/course/grade-flow-page.html b/course/templates/course/grade-flow-page.html index abc3c700e5796e2c894b935b4ee77688b89789b1..8345fbd9ad8d637a418d473f73c294e8b69fdd69 100644 --- a/course/templates/course/grade-flow-page.html +++ b/course/templates/course/grade-flow-page.html @@ -97,12 +97,20 @@
{% trans "User ID" %} | -{% trans "Name" context "real name of a user" %} | + {% if not pperm.view_participant_masked_profile %} +{% trans "User ID" %} | +{% trans "Name" context "real name of a user" %} | + {% else %} +{% trans "User" %} | + {% endif %} {% if view_page_grades %} {% for i in page_numbers %}{{ i }} | @@ -129,16 +133,29 @@ {% for participation, grade_info in grade_table %}
---|---|---|---|
- {{ participation.user.username }} - | -- - {{ participation.user.get_full_name }} - - {% if participation.role != participation_role.student %} - ({{ participation.role }}) + + {% if not pperm.view_participant_masked_profile %} + {{ participation.user.username }} + {% else %} + {% if participation.user == user %}{% trans "Me" %}{% else %}{{ participation.user.get_masked_profile }}{% endif %} + {% endif %} + + {% if pperm.view_participant_masked_profile %} + {% if participation|has_permission:"view_gradebook" %} + ({{ participation.get_role_desc }}) + {% endif %} {% endif %} - | + + {% if not pperm.view_participant_masked_profile %} ++ + {{ participation.user.get_full_name }} + + {% if participation|has_permission:"view_gradebook" %} + ({{ participation.get_role_desc }}) + {% endif %} + | + {% endif %} {% if view_page_grades %} {% for page_idx, grade in grade_info.grades %}