def post(self, request, *args, **kwargs): """ Through post can generate new attempt. """ thread_content = self.get_object() # if content object isn't an assessment, then return 404 assessment_content_type = ContentType.objects.get(app_label="micourses", model='assessment') if thread_content.content_type != assessment_content_type: raise Http404("No assessment found") assessment=thread_content.content_object course=thread_content.course if thread_content.n_of_object != 1: get_string = "?n=%s" % thread_content.n_of_object else: get_string = "" # determine if user is enrolled in class try: enrollment = course.courseenrollment_set.get( student=request.user.courseuser) except (ObjectDoesNotExist, AttributeError): # if not in course, just redirect to the assessment url return HttpResponseRedirect(reverse('miassess:assessment', kwargs={'course_code': course.code, 'assessment_code': assessment.code })\ + get_string) # get or create content record for user try: student_record = thread_content.contentrecord_set.get( enrollment=enrollment) except ObjectDoesNotExist: with transaction.atomic(), reversion.create_revision(): student_record = thread_content.contentrecord_set.create( enrollment=enrollment) attempts = student_record.attempts.all() from micourses.utils import create_new_assessment_attempt with transaction.atomic(): create_new_assessment_attempt( assessment=assessment, thread_content=thread_content, courseuser = request.user.courseuser, student_record = student_record) # redirect to assessment url return HttpResponseRedirect(reverse('miassess:assessment', kwargs={'course_code': course.code, 'assessment_code': assessment.code })\ + get_string)
def determine_version_attempt(self, user, seed, content_attempt_id, question_attempt_ids): """ Determine what version of assessment to generate. For enrolled students, find or create content attempts and question attempts. Set the following variables that give information about user and the assessment's role in course: self.course_enrollment self.thread_content self.current_attempt Set the following variables that specify the version of assessment: self.assessment_seed self.version self.question_list question_list is a list of dictionaries with the following info about each question - question: the question chosen for the question_set - seed: the seed for the question - question_set (optional): the question set from which the question was chosen - question_attempt (optional): the question attempt in which to record responses. ------------------ Behavior based on status of user as follows. Anonymous user behavior: just set seed via GET. If seed is not specified, use seed=1. If single version, ignore seed from GET and use seed=1. Generate new seed and make link at bottom. Even if resample question sets, reloading page will reset with questions from that given assessment seed. Logged in user who isn't in course: same as anonymous user behavior Logged in user who is an active student of course: If assessment is not in course thread, same as anonymous user behavior Otherwise - Ignore seed from GET - Determine availability of content - Find latest content attempt If content attempt validity does not match, treat as no matching attempt (available=valid) Obtain * assessment seed from content attempt * list of questions sets in order (from content attempt question sets) * the latest question attempt for each question set * from each question attempt, determine + question + seed + whether or not solution viewed If missing data (e.g., assessment seed or question attempts), then treat as though don't have content attempt and create new one - If no matching content attempt, then create new content attempt and question attempts. Generate assessment seed as follows: * If not yet released, set seed to be attempt number. * Otherwise, create assessment seed from + thread content id and attempt number + plus username if assessment is individualized by student Exception: set seed=1 if assessment marked as single version Use assessment seed to generate * list of question sets in order * question and seed Save * assessment seed to content attempt * list of question sets in order to content attempt question sets * questions and their seeds to question attempt If not yet released or past due, mark content attempt and question attempts as invalid. Logged in user who is a withdrawn student of course: Treat like active student, only mark as withdrawn so can display message when submitting responses. Looged in user who is instructor of course: If assessment is not in course thread, same as anonymous user behavior Otherwise: If content_attempt id is in GET - generate assessment based on that content attempt, using the latest question attempts for each question set - if, in addition, GET contains list of question attempt ids, then use those instead, assuming have one for each question set (if don't have one for each question set or question attempts don't belong to content attempt, then ignore and use latest question attempts.) - if content attempt doesn't have associated question attempts for all question sets, then ignore content_attempt id If no valid content_attempt id, but seed is in GET - use that to generate assessment (even if single version) Do not record any responses if generated from content attempt id or seed If no valid content attempt id and seed is not in GET, then treat as student of course """ # sets the following variables self.course_enrollment=None self.thread_content=None self.assessment_seed= None self.version = '' self.current_attempt=None self.question_list = [] from micourses.render_assessments import get_question_list from micourses.models import AVAILABLE, NOT_YET_AVAILABLE ################################# # first, determine status of user ################################# # if course user doesn't exist, then is anonymous user # as logged in users should have a courseuser try: courseuser = user.courseuser except AttributeError: courseuser = None # check if enrolled in course current_role=None if courseuser: try: self.course_enrollment = self.assessment.course\ .courseenrollment_set.get(student=courseuser) except ObjectDoesNotExist: pass else: current_role = courseuser.get_current_role(course=self.assessment.course) ############################################### # first, determine thread_content of assessment ############################################### # thread_content will be None # if assessment is not in thread and number in thread is 1 try: self.thread_content=self.assessment.determine_thread_content( self.number_in_thread) except ObjectDoesNotExist: raise Http404("No assessment found") ######################################################## # generic behavior if not in course or no thread content ######################################################## if not (self.course_enrollment and self.thread_content): if self.assessment.single_version: self.assessment_seed='1' self.version = '' else: if seed is None: self.assessment_seed='1' else: self.assessment_seed=seed self.version = str(self.assessment_seed)[-4:] self.question_list = get_question_list(self.assessment, seed=self.assessment_seed) return ######################################### # instructor behavior with content attempt or seed specified ######################################### if current_role == INSTRUCTOR_ROLE or current_role == DESIGNER_ROLE: content_attempt=None if content_attempt_id is not None: from micourses.models import ContentAttempt try: content_attempt = ContentAttempt.objects.get( id=content_attempt_id) except ObjectDoesNotExist: pass else: if content_attempt.record.content != self.thread_content: content_attempt=None if content_attempt is None: raise ValueError("Content attempt %s does not exist for %s." % \ (content_attempt_id, self.thread_content.get_title())) # if found valid content attempt, # attempt to find valid question attempts if content_attempt: question_attempts = [] if question_attempt_ids: question_attempt_id_list = question_attempt_ids.split(",") from micourses.models import QuestionAttempt for qa_id in question_attempt_id_list: try: qa=QuestionAttempt.objects.get(id=qa_id.strip()) except QuestionAttempt.DoesNotExist: question_attempts=[] break else: question_attempts.append(qa) from micourses.render_assessments import get_question_list_from_attempt self.question_list = get_question_list_from_attempt( assessment=self.assessment, content_attempt=content_attempt, question_attempts=question_attempts) if self.question_list: self.current_attempt=content_attempt # set assessment seed and version string self.assessment_seed = self.current_attempt.seed self.version = self.current_attempt.version return else: raise ValueError("Invalid content attempt %s for %s.<br/>Question attempts don't match." % \ (content_attempt_id, self.thread_content.get_title())) # if don't have valid question list, then generate from seed, if set if seed is not None: self.assessment_seed = seed self.question_list = get_question_list( self.assessment, seed=self.assessment_seed, thread_content=self.thread_content) self.version=str(seed) return ######################################### # enrolled student behavior # (also instructor with no seed or content attempt) ######################################### try: student_record = self.thread_content.contentrecord_set\ .get(enrollment = self.course_enrollment) except ObjectDoesNotExist: with transaction.atomic(), reversion.create_revision(): student_record = self.thread_content.contentrecord_set\ .create(enrollment = self.course_enrollment) assessment_availability = self.thread_content.return_availability(student_record) # treat assessment not set up for recording as not available if not self.thread_content.record_scores: assessment_availability = NOT_YET_AVAILABLE try: latest_attempt = student_record.attempts.latest() except ObjectDoesNotExist: latest_attempt = None else: # will use the latest attempt in the following cases # 1. assessment is not yet available and attempt is not valid # 2. assessment is available and the attempt is valid # 3. assessment is past due (OK for valid or invalid attempt) # This means that invalid past due responses will be added # to a valid attempt. # However, to prevent free practice on an attempt before it # becomes available, # if an assessment is changed # to become not yet available after an valid attempt has begun, # then create a new, invalid, attempt. # This algorithm will make the original valid # attempt no longer available, even when the assessment # becomes available again. # (This situation is not foolproof, as one could get this # free practice if the assessment was not reloaded.) if assessment_availability==NOT_YET_AVAILABLE: if latest_attempt.valid: latest_attempt=False elif assessment_availability==AVAILABLE: if not latest_attempt.valid: latest_attempt=False self.current_attempt=None if latest_attempt: # Verify latest attempt has the right number of # of question sets with question attempts # If so, set as current attempt and populate # question list from that attempt from micourses.render_assessments import get_question_list_from_attempt self.question_list = get_question_list_from_attempt( assessment=self.assessment, content_attempt=latest_attempt) # if found question_list, use latest attempt as current attempt if self.question_list: self.current_attempt = latest_attempt # set assessment seed and version string self.assessment_seed = latest_attempt.seed self.version = latest_attempt.version return # If didn't find a current attempt to use, generate new attempt if not self.current_attempt: from micourses.utils import create_new_assessment_attempt with transaction.atomic(): new_attempt_info = create_new_assessment_attempt( assessment=self.assessment, thread_content=self.thread_content, courseuser = courseuser, student_record = student_record) self.current_attempt = new_attempt_info['new_attempt'] self.question_list = new_attempt_info['question_list'] self.assessment_seed = new_attempt_info['assessment_seed'] self.version = new_attempt_info['version']