Example #1
0
def draft_keyterm(request,
                  course=None,
                  learner=None,
                  entry_point=None,
                  error_message=''):
    """
    The user is in Draft mode: adding the text
    """
    prior = learner.keytermtask_set.filter(keyterm__entry_point=entry_point)
    if prior.count():
        keytermtask = prior[0]
        keyterm = keytermtask.keyterm
    else:
        try:
            keyterm = KeyTermSetting.objects.get(entry_point=entry_point)
        except KeyTermSetting.DoesNotExist:
            logger.error('Draft: An error occurred. [{0}]'.format(learner))
            return HttpResponse('An error occurred.')

    # We have 4 states: set the correct settings (in case page is reloaded here)
    keytermtask.is_in_draft = True
    keytermtask.is_in_preview = False
    keytermtask.is_submitted = False
    keytermtask.is_finalized = False
    keytermtask.save()

    create_hit(request,
               item=keytermtask,
               event='KT-draft',
               user=learner,
               other_info=keyterm.keyterm)

    if keytermtask.image_raw:
        entry_point.file_upload_form = UploadFileForm_file_optional()
    else:
        entry_point.file_upload_form = UploadFileForm_file_required()

    # TODO: real-time saving of the text as it is typed??

    if keytermtask.reference_text == '<no reference>':
        keytermtask.reference_text = ''

    ctx = {
        'error_message': error_message,
        'keytermtask': keytermtask,
        'course': course,
        'entry_point': entry_point,
        'learner': learner,
        'grade_push_url': request.POST.get('lis_outcome_service_url', '')
    }
    return render(request, 'keyterm/draft.html', ctx)
Example #2
0
def submit_keyterm(request, course=None, learner=None, entry_point=None):
    """
    """
    prior = learner.keytermtask_set.filter(keyterm__entry_point=entry_point)
    if prior.count():
        keytermtask = prior[0]
        keyterm = keytermtask.keyterm
    else:
        try:
            keyterm = KeyTermSetting.objects.get(entry_point=entry_point)
        except KeyTermSetting.DoesNotExist:
            logger.error('Submit: An error occurred. [{0}]'.format(learner))
            return HttpResponse('An error occurred.')

    # 1. Now you have the task ``keytermtask``: set state
    # 2. Store new values in the task
    # 3. Process the image (store it too, but only in staging area)
    # 4. Render the template image  (store it too, but only in staging area)
    # 5. Float the submit buttons left and right of each other

    # We have 4 states: set the correct settings (in case page is reloaded here)
    keytermtask.is_in_draft = False
    keytermtask.is_in_preview = False
    keytermtask.is_submitted = True
    keytermtask.is_finalized = False

    valid_tasks = keyterm.keytermtask_set.filter(is_finalized=True)
    NN_to_upload = keyterm.min_submissions_before_voting - valid_tasks.count()

    create_hit(request,
               item=keytermtask,
               event='KT-submit',
               user=learner,
               other_info='')

    # Get all other user's keyterms: how many othersare uploaded already?
    ctx = {
        'keytermtask': keytermtask,
        'course': course,
        'entry_point': entry_point,
        'learner': learner,
        'NN_to_upload_still': max(0, NN_to_upload),
        'total_finalized': valid_tasks.count(),
        'grade_push_url': request.POST.get('lis_outcome_service_url', '')
    }
    return render(request, 'keyterm/submit.html', ctx)
Example #3
0
def get_create_student(request, course, gfp):
    """
    Gets or creates the learner from the POST request.
    """
    newbie = False

    email = request.POST.get('lis_person_contact_email_primary', '')
    display_name = request.POST.get('lis_person_name_full', '')
    person_firstname = request.POST.get('lis_person_name_given', '')
    person_lastname = request.POST.get('lis_person_name_family', '')
    user_ID = request.POST.get('user_id', '')
    POST_role = request.POST.get('roles', '')

    # You can also use: request.POST['ext_d2l_role'] in Brightspace
    role = 'Student'
    if 'Instructor' in POST_role:
        role = 'Admin'

    learner, newbie = Person.objects.get_or_create(user_ID=user_ID,
                                                   role=role)

    if newbie:
        create_hit(request, item=course, action='create',
                       user=learner, other_info='gfp.id={0}'.format(gfp.id),
                       other_info_id=gfp.id)
        learner.display_name = display_name
        learner.person_firstname = person_firstname
        learner.person_lastname = person_lastname
        learner.save()
        logger.info('New learner: {0} [{1}]'.format(learner.display_name,
                                                    learner.email))

    if learner:
        # Augments the learner with extra fields that might not be there
        learner.user_ID = learner.user_ID or user_ID
        learner.email = learner.email or email
        learner.role = role
        learner.save()


    # Register that this person is allowed into this course.
    Allowed.objects.get_or_create(person=learner, course=course, allowed=True)

    return learner
Example #4
0
def keyterm_startpage(request):
    logger.debug(str(request))
    if request.method != 'POST' and (len(request.GET.keys()) == 0):
        return HttpResponse("You have reached the KeyTerms LTI component.")

    person_or_error, course, pr = starting_point(request)

    if course:
        if isinstance(pr, str) and isinstance(course, Course):
            #TODO: return admin_create_keyterm(request)

            # request.POST.get('custom_keyterm', '')  -> Third Culture Kid
            return HttpResponse('<b>Create a KeyTerm first.</b> ' + pr)

    if not (isinstance(person_or_error, Person)):
        return person_or_error  # Error path if student does not exist

    learner = person_or_error
    logger.debug('Learner entering [pr={0}]: {1}'.format(pr.title, learner))

    from datetime import timedelta
    from django.utils import timezone
    from django_q.tasks import async, schedule
    from django_q.models import Schedule

    msg = 'Welcome to our website at {0}'.format(now())
    # send this message right away
    async ('django.core.mail.send_mail',
           'Welcome',
           msg,
           '*****@*****.**', [learner.email],
           task_name='Welcome email')
    # and this follow up email in one hour
    msg = 'Here are some tips to get you started...{0}'.format(now())
    schedule('django.core.mail.send_mail',
             'Follow up',
             msg,
             '*****@*****.**', [learner.email],
             schedule_type=Schedule.ONCE,
             next_run=timezone.now() + timedelta(seconds=120),
             task_name='Follow-up email')

    create_hit(
        request,
        item=learner,
        event='login',
        user=learner,
    )

    task = KeyTerm_Task.objects.filter(resource_link_page_id=pr.LTI_id)

    # A KeyTerm_task has not been created for this page yet.
    if task.count() == 0:
        #return admin_create_keyterm(request)
        keyterm = request.POST.get('keyterm', '')
        if keyterm:
            admin_create_keyterm(request, keyterm=keyterm)

    terms_per_page = KeyTerm_Definition.objects.filter(keyterm_required=task)

    if terms_per_page.filter(person=learner, is_finalized=True).count() == 0:
        # If no keyterm (for this student) and (for this page), then they must
        # create one first.
        return resume_or_fill_in_keyterm(request, terms_per_page, learner)

    else:
        # If a keyterm for this student, for this page, exists, then show the
        # keyterms from student and their co-learners.
        return show_vote_keyterms(request, terms_per_page, learner)

    return HttpResponse(out)
Example #5
0
def vote_keyterm(request, learner_hash=''):
    """
    POST request recieved when the user votes on an item. In the POST we get the
    keytermtask's ``lookup_hash``, and in the POST's URL we get the learner's
    hash. From this we can determine who is voting on what.
    """
    learner = Person.objects.filter(hash_code=learner_hash)
    if learner.count() != 1:
        logger.error('Learner error hash={}; POST=[{}]'.format(
            learner_hash, request.POST))
        return HttpResponse(('Error: could not identify who you are. Please '
                             'reload the page.'))
    else:
        learner = learner[0]

    lookup_hash = request.POST.get('voting_task', None)
    keytermtask = KeyTermTask.objects.filter(lookup_hash=lookup_hash)
    if keytermtask.count() != 1:
        logger.error('Keytermtask error hash={}; POST=[{}]'.format(
            lookup_hash, request.POST))
        return HttpResponse(
            ('Error: could not identify who you are voting for.'
             ' Please reload the page.'))
    else:
        keytermtask = keytermtask[0]

    # Fail safe: rather not vote for the post, so assume it was 'true'
    prior_state = request.POST.get('state', 'true') == 'true'
    new_state = not (prior_state)

    # Before creating the vote; check if learner should be allowed to vote:
    # Own vote?
    valid_vote = ''
    html_class = ''
    max_votes = keytermtask.keyterm.max_thumbs
    prior_votes = Thumbs.objects.filter(
        voter=learner,
        awarded=True,
        vote_type='thumbs-up',
        keytermtask__keyterm=keytermtask.keyterm)

    if learner == keytermtask.learner:
        valid_vote = 'You may not vote for your own work.'
        html_class = 'warning'
        new_state = False
        logger.error('This should not occur, but is a safeguard. Investigate!')

    # Past the deadline?
    elif timezone.now() > keytermtask.keyterm.deadline_for_voting:
        valid_vote = 'The deadline to vote has passed; sorry.'
        html_class = 'warning'
        new_state = prior_state  # do nothing

    elif not (valid_vote):
        prior_vote = learner.thumbs_set.filter(keytermtask=keytermtask,
                                               vote_type='thumbs-up')
        if prior_vote:
            thumb = prior_vote[0]
            thumb.awarded = new_state
            thumb.save()
        else:
            thumb, _ = Thumbs.objects.get_or_create(
                keytermtask=keytermtask,
                voter=learner,
                awarded=new_state,
                vote_type='thumbs-up',
            )
            thumb.save()

        # One last check (must come after voting!)
        # Too many votes already for others in this same keyterm?
        if prior_votes.count() > max_votes:
            # Undo the prior voting to get the user back to the level allowed
            thumb.awarded = False
            new_state = False
            valid_vote = 'All votes have been used up. '
            html_class = 'warning'
            thumb.save()

    logger.debug('Vote for [{}] by [{}]; new_state: {}'.format(
        lookup_hash, learner_hash, new_state))

    if new_state:
        short_msg = 'Vote recorded; '
    else:
        short_msg = 'Vote withdrawn; '
    if valid_vote == 'All votes have been used up. ':
        short_msg = valid_vote

    message = 'As of {}: you have '.format(timezone.now().strftime(\
                                                 '%d %B %Y at %H:%M:%S (UTC)'))
    if max_votes == prior_votes.count():
        message += ('no more votes left. You may withdraw prior votes by '
                    'clicking on the icon. ')
        short_msg += ('Zero votes left. <br><br>Remove prior votes to '
                      'vote again. ')
    elif (max_votes - prior_votes.count()) == 1:
        message += '1 more vote left. '
        short_msg += '1 more vote left. '
    else:
        message += '{} more votes left. '.format(max_votes -
                                                 prior_votes.count())
        short_msg += '{} more votes left. '.format(max_votes -
                                                   prior_votes.count())

    if valid_vote:
        message += ' <span class="{}">{}</span>'.format(html_class, valid_vote)

    create_hit(request,
               item=keytermtask,
               event='KT-vote',
               user=learner,
               other_info=message)

    response = {
        'message': message,
        'new_state': new_state,
        'task_hash': '#' + lookup_hash,
        'short_msg': short_msg
    }

    return JsonResponse(response)
Example #6
0
def finalize_keyterm(request, course=None, learner=None, entry_point=None):
    """
    """
    prior = learner.keytermtask_set.filter(keyterm__entry_point=entry_point)
    if prior.count():
        keytermtask = prior[0]
        keyterm = keytermtask.keyterm
    else:
        try:
            keyterm = KeyTermSetting.objects.get(entry_point=entry_point)
        except KeyTermSetting.DoesNotExist:
            logger.error('Finalize: An error occurred. [{0}]'.format(learner))
            return HttpResponse('An error occurred.')

    # Get all prior keyterms: and show their thumbnails in random order
    # Show how many votes the user has left?
    valid_tasks = keyterm.keytermtask_set.filter(is_finalized=True)
    if learner.role in ('Learn', ):

        # We have 4 states: set the correct settings
        #                                        (in case page is reloaded here)
        keytermtask.is_in_draft = False
        keytermtask.is_in_preview = False
        keytermtask.is_submitted = False
        keytermtask.is_finalized = True
        keytermtask.save()

        own_task = valid_tasks.get(learner=learner)
        valid_tasks = list(valid_tasks)
        valid_tasks.remove(own_task)
        shuffle(valid_tasks)
        valid_tasks.insert(0, own_task)
    else:

        # Administrator's only
        valid_tasks = list(valid_tasks)
        shuffle(valid_tasks)
        if len(valid_tasks) == 0:
            return HttpResponse('There are no completed keyterms yet to view.')
        else:
            # Just grab the first task to use for display purposes and logic.
            keytermtask = valid_tasks[0]

    #Abuse the ``valid_tasks`` here, and add a field onto it that determines
    #if it has been voted on by this user.
    for task in valid_tasks:
        votes = task.thumbs_set.filter(awarded=True)
        task.number_votes = votes.count()
        task.this_learner_voted_it = votes.filter(voter=learner).count() > 0

    # TODO: push the grade async
    grade_push_url = request.POST.get('lis_outcome_service_url', '')
    response = push_grade(learner=learner,
                          grade_value=100,
                          entry_point=entry_point,
                          grade_push_url=grade_push_url,
                          testing=False)

    prior_votes = Thumbs.objects.filter(voter=learner,
                                        awarded=True,
                                        vote_type='thumbs-up',
                                        keytermtask__keyterm=keyterm).count()
    max_votes = keyterm.max_thumbs
    votes_left = max_votes - prior_votes

    logger.debug('Grade for {0} at [{1}]; response: {2}'.format(
        learner, grade_push_url, response))

    after_voting_deadline = False
    if timezone.now() > keytermtask.keyterm.deadline_for_voting:
        after_voting_deadline = True

    create_hit(request,
               item=keytermtask,
               event='KT-final',
               user=learner,
               other_info='Grade push = {}'.format(response))

    ctx = {
        'keytermtask': keytermtask,
        'course': course,
        'entry_point': entry_point,
        'learner': learner,
        'valid_tasks': valid_tasks,
        'votes_left': votes_left,
        'after_voting_deadline': after_voting_deadline,
    }
    return render(request, 'keyterm/finalize.html', ctx)
Example #7
0
def preview_keyterm(request, course=None, learner=None, entry_point=None):
    """
    """

    prior = learner.keytermtask_set.filter(keyterm__entry_point=entry_point)
    if prior.count():
        keytermtask = prior[0]
        keyterm = keytermtask.keyterm
    else:
        try:
            keyterm = KeyTermSetting.objects.get(entry_point=entry_point)
        except KeyTermSetting.DoesNotExist:
            logger.error('Preview: An error occurred. [{0}]'.format(learner))
            return HttpResponse('An error occurred.')

    # 1. Now you have the task ``keytermtask``: set state
    # 2. Store new values in the task
    # 3. Process the image (store it too, but only in staging area)
    # 4. Render the template image  (store it too, but only in staging area)
    # 5. Float the submit buttons left and right of each other

    # We have 4 states: set the correct settings (in case page is reloaded here)
    keytermtask.is_in_draft = True  # intentional
    keytermtask.is_in_preview = True
    keytermtask.is_submitted = False
    keytermtask.is_finalized = False

    # For the ``Submission`` app we need a Trigger. We don't have that, or
    # need it. So abuse ``entry_point`` as the trigger instead.
    # A ``trigger`` needs an entry_point field, so just refer back to itself.
    entry_point.entry_point = entry_point
    entry_point.accepted_file_types_comma_separated = 'JPEG, PNG, JPG'
    entry_point.max_file_upload_size_MB = 5
    entry_point.send_email_on_success = False

    # Get the (prior) image submission
    submission = prior_submission = None
    subs = learner.submission_set.filter(is_valid=True,
                                         entry_point=entry_point)
    subs = subs.order_by('-datetime_submitted')
    if subs.count():
        submission = prior_submission = subs[0]

    error_message = ''
    if request.FILES:

        submit_inst = upload_submission(request,
                                        learner,
                                        trigger=entry_point,
                                        no_thumbnail=False)

        if isinstance(submit_inst, tuple):
            # Problem with the upload
            error_message = submit_inst[1]
            submission = prior_submission
        else:
            # Successfully uploaded a document
            error_message = ''
            submission = submit_inst

        # Now that the image is processed:
        keytermtask.image_raw = submission

    definition_text = request.POST.get('keyterm-definition', '')
    definition_text = definition_text or '<no definition>'
    definition_text = definition_text.replace('\r\n', '\n')
    keytermtask.definition_text = definition_text
    if len(definition_text.replace('\n', '').replace('\r', '')) > 515:
        keytermtask.definition_text = definition_text[0:515] + ' ...'

    explainer_text = request.POST.get('keyterm-explanation', '')
    explainer_text = explainer_text or '<no explanation>'
    explainer_text = explainer_text.replace('\r\n', '\n')
    keytermtask.explainer_text = explainer_text
    if len(explainer_text.replace('\n', '').replace('\r', '')) > 1015:
        keytermtask.explainer_text = explainer_text[0:1015] + ' ...'

    reference_text = request.POST.get('keyterm-reference', '')
    reference_text = reference_text or '<no reference>'
    keytermtask.reference_text = reference_text
    if len(reference_text.replace('\n', '').replace('\r', '')) > 245:
        keytermtask.reference_text = reference_text[0:245] + ' ...'

    keytermtask.save()

    create_hit(request,
               item=keytermtask,
               event='KT-preview',
               user=learner,
               other_info='Error={}'.format(error_message))

    # We have saved, but if there was an error message: go back to DRAFT
    if error_message:
        return draft_keyterm(request,
                             course=course,
                             learner=learner,
                             entry_point=entry_point,
                             error_message=error_message)
    else:
        create_preview(keytermtask)

    ctx = {
        'keytermtask': keytermtask,
        'course': course,
        'entry_point': entry_point,
        'learner': learner,
        'grade_push_url': request.POST.get('lis_outcome_service_url', '')
    }
    return render(request, 'keyterm/preview.html', ctx)
def handle_review(request, ractual_code):
    """
    From the unique URL:
    1. Get the ``RubricActual`` instance
    2. Format the text for the user

    The user coming here is either:
    a) reviewing a peer's report (peer-review)
    b) the submitter, looking at their peers' feedback (peer-review, read-only)
    """
    show_feedback = False  # True for case (b) above, else it is case (a)
    report = {}
    r_actual, reviewer = get_learner_details(ractual_code)

    # The submitter is coming to read the review.
    if r_actual.status == 'L':
        show_feedback = True

    # NOTE: it is not always the reviewer getting this report!
    #report = get_peer_grading_data(reviewer, feedback_phase)

    # TODO: FUTURE:

    # Use either this code, or the code in models.py, under
    # RubricActual.reports()
    #
    # Right now there is duplicated code.

    # Intentionally put the order_by here, to ensure that any errors in the
    # next part of the code (zip-ordering) are highlighted
    r_item_actuals = r_actual.ritemactual_set.all().order_by('-modified')

    # Ensure the ``r_item_actuals`` are in the right order. These 3 lines
    # sort the ``r_item_actuals`` by using the ``order`` field on the associated
    # ``ritem_template`` instance.
    # I noticed that in some peer reviews the order was e.g. [4, 1, 3, 2]
    r_item_template_order = (i.ritem_template.order for i in r_item_actuals)
    zipped = list(zip(r_item_actuals, r_item_template_order))
    r_item_actuals, _ = list(zip(*(sorted(zipped, key=lambda x: x[1]))))

    has_prior_answers = False
    for item in r_item_actuals:
        item.results = {'score': None, 'max_score': None}
        item_template = item.ritem_template

        #item.options = ROptionTemplate.objects.filter(\
        #                         rubric_item=item_template).order_by('order')
        item.options = item_template.roptiontemplate_set.all().order_by(
            'order')

        for option in item.options:
            #prior_answer = ROptionActual.objects.filter(roption_template=option,
            #                                            ritem_actual=item,
            #                                            submitted=True)
            prior_answer = option.roptionactual_set.filter(submitted=True,
                                                           ritem_actual=item)

            if prior_answer.count():
                has_prior_answers = True
                if item_template.option_type in ('DropD', 'Chcks', 'Radio'):
                    option.selected = True
                    item.results['score'] = option.score
                    item.results['max_score'] = item_template.max_score
                elif item_template.option_type == 'LText':
                    option.prior_text = prior_answer[0].comment

        # Store the peer- or self-review results in the item; to use in the
        # template to display the feedback.
        #item.results = report.get(item_template, [[], None, None, None, []])

        # Randomize the comments and numerical scores before returning.
        #shuffle(item.results[0])
        #if item_template.option_type == 'LText':
        #    item.results[0] = '\n----------------------\n'.join(item.results[0])
        #    item.results[4] = '\n'.join(item.results[4])

    #review_items, _ = r_actual.report()

    if has_prior_answers and (show_feedback == False):
        create_hit(request,
                   item=r_actual,
                   event='continue-review-session',
                   user=reviewer,
                   other_info='Returning back')

    elif has_prior_answers and show_feedback:
        create_hit(request,
                   item=r_actual,
                   event='viewing-review-session',
                   user=reviewer,
                   other_info='Showing readonly')

    else:
        r_actual.status = 'V'
        r_actual.save()

        logger.debug('Start-review: {0}'.format(reviewer))
        create_hit(request,
                   item=r_actual,
                   event='start-a-review-session',
                   user=reviewer,
                   other_info='Fresh start')

    ctx = {
        'ractual_code': ractual_code,
        'submission': r_actual.submission,
        'person': reviewer,
        'r_item_actuals': r_item_actuals,
        'rubric_template': r_actual.rubric_template,
        'report': report,
        'show_feedback': show_feedback,
        'show_special': False,
    }
    return render(request, 'rubric/review_peer.html', ctx)
def submit_peer_review_feedback(request, ractual_code):
    """
    Learner is submitting the results of their evaluation.
    """
    # 1. Check that this is POST
    # 2. Create OptionActuals
    # 3. Calculate score for evaluations?
    logger.info('Submitting a review: {}'.format(ractual_code))
    r_actual, reviewer = get_learner_details(ractual_code)
    if reviewer is None:
        # This branch only happens with error conditions.
        return r_actual

    if r_actual.status == 'L':
        logger.info('FYI: Locked review message was displayed')
        return render(request, 'rubric/locked.html', {})

    r_item_actuals = r_actual.ritemactual_set.all()

    items = {}
    # Create the dictionary: one key per ITEM.
    # The value associated with the key is a dictionary itself.
    # items[1] = {'options': [....]  <-- list of the options associated
    #             'item_obj': the item instance / object}
    #

    for item in r_item_actuals:
        item_template = item.ritem_template
        item_dict = {}
        items[item_template.order] = item_dict
        item_dict['options'] = item_template.roptiontemplate_set.all()\
                                                          .order_by('order')
        item_dict['item_obj'] = item
        item_dict['template'] = item_template

    # Stores the users selections as "ROptionActual" instances
    word_count = 0
    total_score = 0.0
    logger.debug('About to process each key')
    for key in request.POST.keys():

        # Small glitch: a set of checkboxes, if all unselected, will not appear
        # here, which means that that item will not be "unset".

        # Process each item in the rubric, one at a time. Only ``item``
        # values returned in the form are considered.
        if not (key.startswith('item-')):
            continue

        item_num, score, words = process_POST_review(
            key, request.POST.getlist(key, None), items)

        # Only right at the end: if all the above were successful:
        if (item_num
                is not False):  # explicitly check, because here 0 \= False
            items.pop(item_num)  # this will NOT pop if "item_num=False"
            # which happens if an empty item is processed
        total_score += score
        word_count += words

        logger.debug('Processed: [s={}][w={}]'.format(score, words))

    # All done with storing the results. Did the user fill everything in?
    filled_in = RubricActual.objects.filter(
        Q(status='C') | Q(status='L'),
        rubric_template=r_actual.rubric_template)

    logger.debug('Found filled in RActuals: {}'.format(str(filled_in)))
    words = [r.word_count for r in filled_in]
    words = np.array(words)
    words = words[words != 0]
    if len(words) == 0:
        median_words = 242
    else:
        # Avoids an error on certain Numpy installations
        median_words = np.round(np.median(words))

    logger.debug('Median so far: {}'.format(median_words))
    logger.info('r_actual.special_access: {}'.format(r_actual.special_access))

    if request.POST:
        request.POST._mutable = True
        request.POST.pop('csrfmiddlewaretoken',
                         None)  # don't want this in stats
        request.POST._mutable = False
    if len(items) == 0 or r_actual.special_access:
        # Once we have processed all options, and all items, the length is
        # zero, so we can also:
        r_actual.submitted = True
        r_actual.completed = timezone.now()
        r_actual.status = 'C'  # completed
        r_actual.word_count = word_count
        r_actual.score = total_score
        r_actual.save()

        # Call the hook function here, asynchronously, to hand-off any
        # functionality that is needed for next steps, eg. PDF creation, etc.

        func = getattr(sys.modules['interactive.views'],
                       r_actual.rubric_template.hook_function, None)
        if func:
            func(r_actual=r_actual)


        logger.debug('ALL-DONE: {0}. Median={1} vs Actual={2}; Score={3}'\
                     .format(reviewer, median_words, word_count, total_score))
        create_hit(request,
                   item=r_actual,
                   event='ending-a-review-session',
                   user=reviewer,
                   other_info=('COMPLETE; Median={0} vs '
                               'Actual={1}; Score={2}||').format(
                                   median_words, word_count, total_score) +
                   str(request.POST))
    else:
        r_actual.submitted = False
        r_actual.completed = r_actual.started
        r_actual.status = 'P'  # Still in progress
        r_actual.word_count = word_count
        r_actual.save()
        create_hit(request, item=r_actual, event='ending-a-review-session',
                   user=reviewer, other_info='MISSING {0}'.format(len(items))\
                                                     + str(request.POST)  )
        logger.debug('MISSING[{0}]: {1}'.format(len(items), reviewer))
    try:
        percentage = total_score / r_actual.rubric_template.maximum_score * 100
    except ZeroDivisionError:
        percentage = 0.0

    ctx = {
        'n_missing': len(items),
        'r_actual': r_actual,
        'median_words': median_words,
        'word_count': word_count,
        'person': reviewer,
        'total_score': total_score,
        'percentage': percentage,
    }
    ctx['thankyou'] = insert_evaluate_variables(\
                                r_actual.rubric_template.thankyou_template,
                                ctx)
    return render(request, 'rubric/thankyou_problems.html', ctx)
def successful_submission(request, course_code_slug, question_set_slug):
    """
    User has successfully saved their answers.
    """
    quests = validate_user(request, course_code_slug, question_set_slug)
    if isinstance(quests, HttpResponse):
        return quests

    # Mark every question as successfully submitted
    for quest in quests:
        quest.is_submitted = True
        quest.save()

    if quests:
        create_hit(request, quests[0].qset, extra_info='Submitted answers')

    token = request.session['token']

    user = request.user.profile
    final = quests[0].qset.ans_time_final.strftime('%H:%M:%S on %d %h %Y')
    token_obj = Token.objects.filter(token_address=token).filter(user=user)

    if token_obj:
        token_obj[0].has_been_used = True
        token_obj[0].save()

    TimerStart.objects.create(event='submit-qset',
                              user=user,
                              profile=get_profile(request),
                              item_pk=quests[0].qset.id,
                              item_type='QSet',
                              referrer=request.META.get('HTTP_REFERER', ''))

    # Send an email
    to_address = request.user.profile.user.email
    message = """\
    This message confirms that you have succesfully submitted the responses to
    the questions.

    If you have time left on the test, you may sign in again and update any of
    your answers. But like a regular exam, once YOUR time is up, you cannot
    change your answers, even if it is before the cut-off time.

    Solutions will be available after the cut-off time, %s.

    The http://quest.mcmaster.ca web server.
    """ % quests[0].qset.ans_time_final.strftime('%H:%M on %d %h %Y')
    subject = 'Succesful Quest submission: %s' % quests[0].qset.name

    out = send_email([
        to_address,
    ], subject, message)
    if out:
        logger.debug('Successfully sent email on QSet submission: %s' %
                     str(to_address))
    else:
        logger.error('Unable to send submission confirmation email to: %s' %
                     str(to_address))

    ctxdict = {'token': token, 'quest_cut_off': final}
    return render_to_response('question/successfully-submitted.html',
                              ctxdict,
                              context_instance=RequestContext(request))
def ask_specific_question(request, course_code_slug, question_set_slug,
                          question_id):
    """
    Asks a specific question to the user.

    There is also extensive validation done in this function.
    """
    quests = validate_user(request, course_code_slug, question_set_slug,
                           question_id)
    if isinstance(quests, HttpResponse):
        return quests
    if isinstance(quests, tuple):
        quests, q_id = quests

    quest = quests[q_id - 1]
    create_hit(request, quest, extra_info=None)
    html_question = quest.as_displayed
    q_type = quest.qtemplate.q_type

    # Has the user answered this question (even temporarily?).
    if quest.given_answer:
        html_question = update_with_current_answers(quest)

        #if q_type in ('mcq', 'tf'):
        #html_question = re.sub(r'"'+quest.given_answer+r'"',
        #r'"'+quest.given_answer+r'" checked',
        #html_question)

        #if q_type in ('multi', ):
        #for selection in quest.given_answer.split(','):
        #html_question = re.sub(r'"'+selection+r'"',
        #r'"'+selection+r'" checked',
        #html_question)

        #if q_type in ('long'):
        #re_exp = TEXTAREA_RE.search(html_question)
        #if re_exp:
        #html_question = '%s%s%s' % (html_question[0:re_exp.start(2)],
        #quest.given_answer,
        #html_question[re_exp.end(2):])

        #if q_type in ('short'):
        #out = ''
        #if INPUT_RE.findall(html_question):
        ## INPUT_RE = (r'\<input(.*?)name="(.*?)"(.*?)\</input\>')
        #start = 0
        #token_dict = json.loads(quest.given_answer)
        #for item in INPUT_RE.finditer(html_question):
        #val = token_dict.get(item.group(2), '')
        #out += '%s%s%s%s%s%s' % \
        #(html_question[start:item.start()],
        #r'<input',
        #' value="%s"' % val,
        #' name="%s"' % item.group(2),
        #item.group(3),
        #r'</input>')

        #start = item.end()

        #if out:
        #out += html_question[start:]

        #html_question = out

    # Validation types:
    show_solution = show_question = False
    fields_disabled = True
    event_type = ''
    other_info = ''
    item_pk = quest.id
    item_type = 'QActual'
    min_remain = sec_remain = 0
    course = Course.objects.filter(slug=course_code_slug)[0]
    qset = quests[0].qset
    now_time = datetime.datetime.now()

    if qset.ans_time_start.replace(tzinfo=None) > now_time:
        # Test has not started yet; throw "too-early"
        show_question = False

    elif qset.ans_time_final.replace(tzinfo=None) <= now_time:
        # Test is finished; show the questions, fields disabled, with solutions
        show_solution = True
        show_question = True
        final_time = now_time + datetime.timedelta(seconds=60 * 60)

    else:
        # We are in the middle of the QSet testing period: show question but
        # no solution.
        # Check for a Timing object.
        #    If present,
        #        Are we within the USERS time window
        #           Y : allow question to be answered
        #           N : throw error: time has expired.
        #    If not present:
        #        create one

        show_question = True
        fields_disabled = False

        timing_obj = Timing.objects.filter(user=request.user.profile,
                                           qset=qset)
        if timing_obj:
            tobj = timing_obj[0]
            event_type = 'attempting-quest'

            if tobj.final_time <= now_time:
                # Either the user doesn't have the expiry date set in their
                # session (i.e. they logged out and then refreshed the page)
                # or the expiry has past the current time
                exp = tobj.final_time.strftime('%H:%M:%S on %d %h %Y')
                ctxdict = {
                    'time_expired': exp,
                    'solution_time': qset.ans_time_final
                }
                ctxdict.update(csrf(request))
                return render_to_response(
                    'question/time-expired.html',
                    ctxdict,
                    context_instance=RequestContext(request))

        else:
            # Create the timing object, starting from right now
            final = qset.duration()
            intend_finish = now_time + \
                datetime.timedelta(hours=final.hour) + \
                datetime.timedelta(minutes=final.minute) + \
                datetime.timedelta(seconds=final.second)

            # OLD, crufty code. We no longer use qset.max_duration
            #if qset.max_duration == datetime.time(0, 0, 0):
            ## Not sure why this is checked; guess it is incase the admin
            ## user has forgot to specify the maximum time duration
            ## Maybe it was a method to handle the case where the test was
            ## as long in duration as the start to final time??
            #final_time = qset.ans_time_final
            #else:
            ## Finish before the test if over, or earlier

            final_time = min(intend_finish, qset.ans_time_final)

            token = request.session.get('token', None)
            if token:
                event_type = 'start-a-quest-session'
                other_info = 'Starting QSet; creating Timing object'
                token_obj = Token.objects.filter(token_address=token)
                tobj = Timing.objects.create(user=request.user.profile,
                                             qset=qset,
                                             start_time=now_time,
                                             final_time=final_time,
                                             token=token_obj[0])

        if tobj.final_time > now_time:
            delta = tobj.final_time - now_time
            extra = 0
            if delta.days:
                extra = 60 * 24 * delta.days
            min_remain = int(floor(delta.seconds / 60.0)) + extra
            sec_remain = int(delta.seconds - min_remain * 60)

    # Now perform various actions depending on the authorizations above
    if not (show_question):
        ctxdict = {'time_to_start': qset.ans_time_start}
        ctxdict.update(csrf(request))
        return render_to_response('question/not-started-yet.html',
                                  ctxdict,
                                  context_instance=RequestContext(request))

    if fields_disabled:
        # Make the inputs disabled when displaying solutions:
        html_question = re.sub(r'<input', (r'<input disabled="true" '
                                           r'style="color: #B00"'),
                               html_question)

        if q_type in ('long', 'peer-eval'):
            html_question = re.sub(r'<textarea', r'<textarea disabled="true"',
                                   html_question)

    if quest.qtemplate.disable_solution_display:
        show_solution = False
        html_solution = 'The solution is disabled for this question.'

    elif show_solution:
        event_type = 'review-a-quest-question-post'
        other_info = 'Token = %s' % request.session.get('token', '')
        html_solution = quest.html_solution

    else:
        other_info = 'QActual=[%d]; current answer: %s' % \
            (quest.id, quest.given_answer[0:4900])
        html_solution = ''  # don't show the solutions yet

    if event_type:
        TimerStart.objects.create(event=event_type,
                                  user=request.user.profile,
                                  profile=get_profile(request),
                                  item_pk=item_pk,
                                  item_type=item_type,
                                  referrer=request.META.get(
                                      'HTTP_REFERER', '')[0:510],
                                  other_info=other_info)
    else:
        pass

    ctxdict = {
        'quest_list': quests,
        'item_id': q_id,
        'course_name': course,
        'course': course_code_slug,
        'qset_name': qset.name,
        'qset': question_set_slug,
        'item': quest,
        'timeout_time': 500,  # in the HTML template, XHR timeout
        'minutes_left': min_remain,
        'seconds_left': sec_remain,
        'html_question': html_question,
        'html_solution': html_solution,
        'last_question': q_id == len(quests),
        'prior_feedback': quest.feedback or ''
    }
    ctxdict.update(csrf(request))
    return render_to_response('question/single-question.html',
                              ctxdict,
                              context_instance=RequestContext(request))