Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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']