From 047f1ea664090d6cf91073028f3ae5684a0d8db7 Mon Sep 17 00:00:00 2001 From: Fernando <fernando.gonzalez@edunext.co> Date: Mon, 16 Oct 2023 11:21:04 -0400 Subject: [PATCH] feat: endpoint /api/ora_staff_grader/initialize upgraded --- openassessment/data.py | 42 +++++++++++++ .../serializers/submission_list.py | 61 +++++++++++++++++-- .../staffgrader/staff_grader_mixin.py | 33 +++++++--- 3 files changed, 123 insertions(+), 13 deletions(-) diff --git a/openassessment/data.py b/openassessment/data.py index fb8c1823a7..b1ddc7a25c 100644 --- a/openassessment/data.py +++ b/openassessment/data.py @@ -95,6 +95,48 @@ def map_anonymized_ids_to_usernames(anonymized_ids): return anonymous_id_to_username_mapping +def map_anonymized_ids_to_emails(anonymized_ids): + """ + Args: + anonymized_ids - list of anonymized user ids. + Returns: + dictionary, that contains mapping between anonymized user ids and + actual user emails. + """ + User = get_user_model() + + users = _use_read_replica( + User.objects.filter(anonymoususerid__anonymous_user_id__in=anonymized_ids) + .annotate(anonymous_id=F("anonymoususerid__anonymous_user_id")) + .values("email", "anonymous_id") + ) + anonymous_id_to_email_mapping = { + user["anonymous_id"]: user["email"] for user in users + } + return anonymous_id_to_email_mapping + + +def map_anonymized_ids_to_fullname(anonymized_ids): + """ + Args: + anonymized_ids - list of anonymized user ids. + Returns: + dictionary, that contains mapping between anonymized user ids and + actual user fullname. + """ + User = get_user_model() + + users = _use_read_replica( + User.objects.filter(anonymoususerid__anonymous_user_id__in=anonymized_ids) + .select_related("profile") + .annotate(anonymous_id=F("anonymoususerid__anonymous_user_id")) + .values("profile__name", "anonymous_id") + ) + + anonymous_id_to_fullname_mapping = { + user["anonymous_id"]: user["profile__name"] for user in users + } + return anonymous_id_to_fullname_mapping class CsvWriter: """ diff --git a/openassessment/staffgrader/serializers/submission_list.py b/openassessment/staffgrader/serializers/submission_list.py index 37c7944eb0..6e90de4d81 100644 --- a/openassessment/staffgrader/serializers/submission_list.py +++ b/openassessment/staffgrader/serializers/submission_list.py @@ -30,7 +30,9 @@ class Meta: 'gradedBy', 'username', 'teamName', - 'score' + 'score', + "email", + "fullname", ] read_only_fields = fields @@ -40,17 +42,23 @@ class Meta: CONTEXT_ANON_ID_TO_USERNAME = 'anonymous_id_to_username' CONTEXT_SUB_TO_ASSESSMENT = 'submission_uuid_to_assessment' CONTEXT_SUB_TO_ANON_ID = 'submission_uuid_to_student_id' + CONTEXT_ANON_ID_TO_EMAIL = "anonymous_id_to_email" + CONTEXT_ANON_ID_TO_FULLNAME = "anonymous_id_to_fullname" def _verify_required_context(self, context): """Verify that required individual or team context is present for serialization""" context_keys = set(context.keys()) # Required context for individual submissions - required_context = set([ - self.CONTEXT_ANON_ID_TO_USERNAME, - self.CONTEXT_SUB_TO_ASSESSMENT, - self.CONTEXT_SUB_TO_ANON_ID - ]) + required_context = set( + [ + self.CONTEXT_ANON_ID_TO_USERNAME, + self.CONTEXT_SUB_TO_ASSESSMENT, + self.CONTEXT_SUB_TO_ANON_ID, + self.CONTEXT_ANON_ID_TO_EMAIL, + self.CONTEXT_ANON_ID_TO_FULLNAME, + ] + ) missing_context = required_context - context_keys if missing_context: @@ -70,6 +78,8 @@ def __init__(self, *args, **kwargs): username = serializers.SerializerMethodField() teamName = serializers.SerializerMethodField() score = serializers.SerializerMethodField() + email = serializers.SerializerMethodField() + fullname = serializers.SerializerMethodField() def _get_username_from_context(self, anonymous_user_id): try: @@ -85,6 +95,22 @@ def _get_anonymous_id_from_context(self, submission_uuid): f"No submitter anonymous user id found for submission uuid {submission_uuid}" ) from e + def _get_email_from_context(self, anonymous_user_id): + try: + return self.context[self.CONTEXT_ANON_ID_TO_EMAIL][anonymous_user_id] + except KeyError as e: + raise MissingContextException( + f"Email not found for anonymous user id {anonymous_user_id}" + ) from e + + def _get_fullname_from_context(self, anonymous_user_id): + try: + return self.context[self.CONTEXT_ANON_ID_TO_FULLNAME][anonymous_user_id] + except KeyError as e: + raise MissingContextException( + f"fullname not found for anonymous user id {anonymous_user_id}" + ) from e + def get_dateGraded(self, workflow): return str(workflow.grading_completed_at) @@ -99,6 +125,16 @@ def get_username(self, workflow): self._get_anonymous_id_from_context(workflow.identifying_uuid) ) + def get_email(self, workflow): + return self._get_email_from_context( + self._get_anonymous_id_from_context(workflow.identifying_uuid) + ) + + def get_fullname(self, workflow): + return self._get_fullname_from_context( + self._get_anonymous_id_from_context(workflow.identifying_uuid) + ) + def get_teamName(self, workflow): # pylint: disable=unused-argument # For individual submissions, this is intentionally empty return None @@ -123,12 +159,16 @@ class TeamSubmissionListSerializer(SubmissionListSerializer): CONTEXT_SUB_TO_ASSESSMENT = 'submission_uuid_to_assessment' CONTEXT_SUB_TO_TEAM_ID = 'team_submission_uuid_to_team_id' CONTEXT_TEAM_ID_TO_TEAM_NAME = 'team_id_to_team_name' + CONTEXT_ANON_ID_TO_EMAIL = "anonymous_id_to_email" + CONTEXT_ANON_ID_TO_FULLNAME = "anonymous_id_to_fullname" REQUIRED_CONTEXT_KEYS = [ CONTEXT_ANON_ID_TO_USERNAME, CONTEXT_SUB_TO_ASSESSMENT, CONTEXT_SUB_TO_TEAM_ID, CONTEXT_TEAM_ID_TO_TEAM_NAME, + CONTEXT_ANON_ID_TO_EMAIL, + CONTEXT_ANON_ID_TO_FULLNAME, ] def _verify_required_context(self, context): @@ -160,6 +200,15 @@ def get_username(self, workflow): # pylint: disable=unused-argument # For team submissions, this is intentionally empty return None + def get_email(self, workflow): # pylint: disable=unused-argument + # For team submissions, this is intentionally empty + return None + + def get_fullname(self, workflow): # pylint: disable=unused-argument + # For team submissions, this is intentionally empty + return None + + def get_teamName(self, workflow): return self._get_team_name_from_context( self._get_team_id_from_context(workflow.identifying_uuid) diff --git a/openassessment/staffgrader/staff_grader_mixin.py b/openassessment/staffgrader/staff_grader_mixin.py index ae37eec386..639e3ddda1 100644 --- a/openassessment/staffgrader/staff_grader_mixin.py +++ b/openassessment/staffgrader/staff_grader_mixin.py @@ -15,7 +15,8 @@ from openassessment.assessment.models.base import Assessment, AssessmentPart from openassessment.assessment.models.staff import StaffWorkflow, TeamStaffWorkflow -from openassessment.data import map_anonymized_ids_to_usernames, OraSubmissionAnswerFactory, VersionNotFoundException +from openassessment.data import (OraSubmissionAnswerFactory, VersionNotFoundException, map_anonymized_ids_to_emails, + map_anonymized_ids_to_fullname, map_anonymized_ids_to_usernames) from openassessment.staffgrader.errors.submission_lock import SubmissionLockContestedError from openassessment.staffgrader.models.submission_lock import SubmissionGradingLock from openassessment.staffgrader.serializers import ( @@ -215,7 +216,15 @@ def _get_list_workflows_serializer_context(self, staff_workflows, is_team_assign team_id_to_team_name = self.teams_service.get_team_names(course_id, topic_id) # Do bulk lookup for scorer anonymous ids (submitting team name is a separate lookup) - anonymous_id_to_username = map_anonymized_ids_to_usernames(set(workflow_scorer_ids)) + anonymous_id_to_username = map_anonymized_ids_to_usernames( + set(workflow_scorer_ids) + ) + anonymous_id_to_email = map_anonymized_ids_to_emails( + set(workflow_scorer_ids) + ) + anonymous_id_to_fullname = map_anonymized_ids_to_fullname( + set(workflow_scorer_ids) + ) context = { 'team_submission_uuid_to_team_id': team_submission_uuid_to_team_id, @@ -233,6 +242,13 @@ def _get_list_workflows_serializer_context(self, staff_workflows, is_team_assign anonymous_id_to_username = map_anonymized_ids_to_usernames( set(submission_uuid_to_student_id.values()) | workflow_scorer_ids ) + anonymous_id_to_email = map_anonymized_ids_to_emails( + set(submission_uuid_to_student_id.values()) | workflow_scorer_ids + ) + + anonymous_id_to_fullname = map_anonymized_ids_to_fullname( + set(submission_uuid_to_student_id.values()) | workflow_scorer_ids + ) context = { 'submission_uuid_to_student_id': submission_uuid_to_student_id, @@ -242,11 +258,14 @@ def _get_list_workflows_serializer_context(self, staff_workflows, is_team_assign # Rubric, Criteria, and Option models submission_uuid_to_assessment = self.bulk_deep_fetch_assessments(staff_workflows) - context.update({ - 'anonymous_id_to_username': anonymous_id_to_username, - 'submission_uuid_to_assessment': submission_uuid_to_assessment, - }) - + context.update( + { + "anonymous_id_to_username": anonymous_id_to_username, + "anonymous_id_to_email": anonymous_id_to_email, + "anonymous_id_to_fullname": anonymous_id_to_fullname, + "submission_uuid_to_assessment": submission_uuid_to_assessment, + } + ) return context def _bulk_fetch_annotated_staff_workflows(self, is_team_assignment=False):