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)
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)
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
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)
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)
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)
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))