def _get_or_create_student_item(student_item_dict): """Gets or creates a Student Item that matches the values specified. Attempts to get the specified Student Item. If it does not exist, the specified parameters are validated, and a new Student Item is created. Args: student_item_dict (dict): The dict containing the student_id, item_id, course_id, and item_type that uniquely defines a student item. Returns: StudentItem: The student item that was retrieved or created. Raises: SubmissionInternalError: Thrown if there was an internal error while attempting to create or retrieve the specified student item. SubmissionRequestError: Thrown if the given student item parameters fail validation. Examples: >>> student_item_dict = dict( >>> student_id="Tim", >>> item_id="item_1", >>> course_id="course_1", >>> item_type="type_one" >>> ) >>> _get_or_create_student_item(student_item_dict) {'item_id': 'item_1', 'item_type': 'type_one', 'course_id': 'course_1', 'student_id': 'Tim'} """ try: try: return StudentItem.objects.get(**student_item_dict) except StudentItem.DoesNotExist: student_item_serializer = StudentItemSerializer( data=student_item_dict ) if not student_item_serializer.is_valid(): logger.error( u"Invalid StudentItemSerializer: errors:{} data:{}".format( student_item_serializer.errors, student_item_dict ) ) raise SubmissionRequestError(field_errors=student_item_serializer.errors) return student_item_serializer.save() except DatabaseError: error_message = u"An error occurred creating student item: {}".format( student_item_dict ) logger.exception(error_message) raise SubmissionInternalError(error_message)
def _get_or_create_student_item(student_item_dict): """Gets or creates a Student Item that matches the values specified. Attempts to get the specified Student Item. If it does not exist, the specified parameters are validated, and a new Student Item is created. Args: student_item_dict (dict): The dict containing the student_id, item_id, course_id, and item_type that uniquely defines a student item. Returns: StudentItem: The student item that was retrieved or created. Raises: SubmissionInternalError: Thrown if there was an internal error while attempting to create or retrieve the specified student item. SubmissionRequestError: Thrown if the given student item parameters fail validation. Examples: >>> student_item_dict = dict( >>> student_id="Tim", >>> item_id="item_1", >>> course_id="course_1", >>> item_type="type_one" >>> ) >>> _get_or_create_student_item(student_item_dict) {'item_id': 'item_1', 'item_type': 'type_one', 'course_id': 'course_1', 'student_id': 'Tim'} """ try: try: return StudentItem.objects.get(**student_item_dict) except StudentItem.DoesNotExist as student_error: student_item_serializer = StudentItemSerializer( data=student_item_dict ) if not student_item_serializer.is_valid(): logger.error( "Invalid StudentItemSerializer: errors:%(errors)s data:%(data)s", { 'errors': student_item_serializer.errors, 'data': student_item_dict, } ) raise SubmissionRequestError(field_errors=student_item_serializer.errors) from student_error return student_item_serializer.save() except DatabaseError as error: error_message = f"An error occurred creating student item: {student_item_dict}" logger.exception(error_message) raise SubmissionInternalError(error_message) from error
def get_course_item_submissions(course_id, item_id, item_type, read_replica=True): submission_qs = Submission.objects if read_replica: submission_qs = _use_read_replica(submission_qs) query = submission_qs.select_related( 'student_item__scoresummary__latest__submission').filter( student_item__course_id=course_id, student_item__item_type=item_type, student_item__item_id=item_id, ).iterator() for submission in query: student_item = submission.student_item serialized_score = {} if hasattr(student_item, 'scoresummary'): latest_score = student_item.scoresummary.latest if (not latest_score.is_hidden() ) and latest_score.submission.uuid == submission.uuid: serialized_score = ScoreSerializer(latest_score).data yield (StudentItemSerializer(student_item).data, SubmissionSerializer(submission).data, serialized_score)
def get_all_course_submission_information(course_id, item_type, read_replica=True): """ For the given course, get all student items of the given item type, all the submissions for those itemes, and the latest scores for each item. If a submission was given a score that is not the latest score for the relevant student item, it will still be included but without score. Args: course_id (str): The course that we are getting submissions from. item_type (str): The type of items that we are getting submissions for. read_replica (bool): Try to use the database's read replica if it's available. Yields: A tuple of three dictionaries representing: (1) a student item with the following fields: student_id course_id student_item item_type (2) a submission with the following fields: student_item attempt_number submitted_at created_at answer (3) a score with the following fields, if one exists and it is the latest score: (if both conditions are not met, an empty dict is returned here) student_item submission points_earned points_possible created_at submission_uuid """ submission_qs = Submission.objects if read_replica: submission_qs = _use_read_replica(submission_qs) query = submission_qs.select_related( 'student_item__scoresummary__latest__submission').filter( student_item__course_id=course_id, student_item__item_type=item_type, ).iterator() for submission in query: student_item = submission.student_item serialized_score = {} if hasattr(student_item, 'scoresummary'): latest_score = student_item.scoresummary.latest # Only include the score if it is not a reset score (is_hidden), and if the current submission is the same # as the student_item's latest score's submission. This matches the behavior of the API's get_score method. if (not latest_score.is_hidden() ) and latest_score.submission.uuid == submission.uuid: serialized_score = ScoreSerializer(latest_score).data yield (StudentItemSerializer(student_item).data, SubmissionSerializer(submission).data, serialized_score)
def get_submission_and_student(uuid, read_replica=False): """ Retrieve a submission by its unique identifier, including the associated student item. Args: uuid (str): the unique identifier of the submission. Kwargs: read_replica (bool): If true, attempt to use the read replica database. If no read replica is available, use the default database. Returns: Serialized Submission model (dict) containing a serialized StudentItem model Raises: SubmissionNotFoundError: Raised if the submission does not exist. SubmissionRequestError: Raised if the search parameter is not a string. SubmissionInternalError: Raised for unknown errors. """ # This may raise API exceptions submission = get_submission(uuid, read_replica=read_replica) # Retrieve the student item from the cache cache_key = "submissions.student_item.{}".format( submission['student_item']) try: cached_student_item = cache.get(cache_key) except Exception: # pylint: disable=broad-except # The cache backend could raise an exception # (for example, memcache keys that contain spaces) logger.exception( "Error occurred while retrieving student item from the cache") cached_student_item = None if cached_student_item is not None: submission['student_item'] = cached_student_item else: # There is probably a more idiomatic way to do this using the Django REST framework try: student_item_qs = StudentItem.objects if read_replica: student_item_qs = _use_read_replica(student_item_qs) student_item = student_item_qs.get(id=submission['student_item']) submission['student_item'] = StudentItemSerializer( student_item).data cache.set(cache_key, submission['student_item']) except Exception as ex: err_msg = f"Could not get submission due to error: {ex}" logger.exception(err_msg) raise SubmissionInternalError(err_msg) from ex return submission
def get_submission_and_student(uuid): """ Retrieve a submission by its unique identifier, including the associated student item. Args: uuid (str): the unique identifier of the submission. Returns: Serialized Submission model (dict) containing a serialized StudentItem model Raises: SubmissionNotFoundError: Raised if the submission does not exist. SubmissionRequestError: Raised if the search parameter is not a string. SubmissionInternalError: Raised for unknown errors. """ # This may raise API exceptions submission = get_submission(uuid) # Retrieve the student item from the cache cache_key = "submissions.student_item.{}".format( submission['student_item']) try: cached_student_item = cache.get(cache_key) except: # The cache backend could raise an exception # (for example, memcache keys that contain spaces) logger.exception( "Error occurred while retrieving student item from the cache") cached_student_item = None if cached_student_item is not None: submission['student_item'] = cached_student_item else: # There is probably a more idiomatic way to do this using the Django REST framework try: student_item = StudentItem.objects.get( id=submission['student_item']) submission['student_item'] = StudentItemSerializer( student_item).data cache.set(cache_key, submission['student_item']) except Exception as ex: err_msg = "Could not get submission due to error: {}".format(ex) logger.exception(err_msg) raise SubmissionInternalError(err_msg) return submission