def given_review_dict(context, pr, lang): tfd = UserDownload.objects.filter(user=pr.reviewer.student.user, assignment=pr.assignment) if tfd: tfd = tfd.first().time_downloaded else: tfd = DEFAULT_DATE r = { 'object': pr, 'lang': lang, 'time_first_download': lang.ftime(tfd), 'time_submitted': lang.ftime(pr.time_submitted), 'rev_items': pr.item_list(), 'pred_hex': encode(pr.assignment.id, context['user_session'].encoder) } # if predecessor has responded, additional fields must be added if pr.time_appraised != DEFAULT_DATE: r['time_appraised'] = lang.ftime(pr.time_appraised) r['app_icon'] = FACES[pr.appraisal] r['app_header'] = lang.phrase('Appraisal_header') % lang.phrase(['ERROR', 'Quite_happy', 'Mixed_feelings', 'Unhappy'][pr.appraisal]) r['imp_opinion'] = lang.phrase('Opinion_on_sucessor_version') % lang.phrase(['Pass', 'No_improvement', 'Minor_changes', 'Good_job'][pr.improvement_appraisal]) # also indicate whether student has acknowledged if pr.time_acknowledged != DEFAULT_DATE: r['time_acknowledged'] = lang.ftime(pr.time_acknowledged) # in case of an appeal, report the appeal status if pr.is_appeal: if pr.time_appeal_assigned != DEFAULT_DATE: r['time_assigned'] = lang.ftime(pr.time_appeal_assigned) ap = Appeal.objects.filter(review=pr).first() if ap: r['referee'] = prefixed_user_name(ap.referee.user) if ap.time_first_viewed != DEFAULT_DATE: r['time_first_viewed'] = lang.ftime(ap.time_first_viewed) if ap.time_decided != DEFAULT_DATE: r['time_decided'] = lang.ftime(ap.time_decided) r['ref_rat'] = ap.grade r['ref_motiv'] = ap.grade_motivation r['penalties'] = lang.penalties_as_text( ap.predecessor_penalty, ap.successor_penalty) # NOTE: in this case, the student ("you") is the successor ... if ap.time_acknowledged_by_successor != DEFAULT_DATE: r['time_your_appr'] = lang.ftime(ap.time_acknowledged_by_successor) r['your_appr_icon'] = FACES[ap.successor_appraisal] r['your_motiv'] = ap.successor_motivation r['you_objected'] = ap.is_contested_by_successor # ... and hence the other party the predecessor if ap.time_acknowledged_by_predecessor != DEFAULT_DATE: r['time_other_appr'] = lang.ftime(ap.time_acknowledged_by_predecessor) r['other_appr_icon'] = FACES[ap.predecessor_appraisal] r['other_motiv'] = ap.predecessor_motivation r['other_objected'] = ap.is_contested_by_predecessor return r
def set_course_json(c, jd): jd['c'] = c.code jd['n'] = c.name jd['m'] = prefixed_user_name(c.manager) jd['d'] = c.description jd['lc'] = c.language.code jd['sd'] = c.language.fdate(c.start_date) jd['ed'] = c.language.fdate(c.end_date) jd['sdi'] = c.start_date.strftime('%Y-%m-%d') jd['edi'] = c.end_date.strftime('%Y-%m-%d') jd['sn'] = c.staff_name jd['sp'] = c.staff_position jd['bc'] = c.badge_color jd['edx'] = c.is_edX jd['hide'] = c.is_hidden
def set_leg_json(el, jd): jd['n'] = el.name jd['r'] = el.rejectable jd['d'] = el.description.replace('<img ', '<img class="ui large image" ') jd['lo'] = el.learning_objectives jd['ui'] = el.upload_instruction.replace('<img ', '<img class="ui large image" ') jd['uits'] = [ui.to_dict() for ui in el.upload_items.all()] jd['f'] = el.required_files jd['ff'] = el.file_list() jd['s'] = el.required_section_title jd['l'] = el.required_section_length jd['k'] = el.required_keywords jd['ut'] = el.min_upload_minutes jd['ri'] = el.review_instruction.replace('<img ', '<img class="ui large image" ') jd['rits'] = [ri.to_dict() for ri in el.review_items.all()] jd['w'] = el.word_count jd['rt'] = el.min_review_minutes jd['se'] = EDIT_STRING % (prefixed_user_name(el.last_editor), timezone.localtime(el.time_last_edit).strftime(DATE_TIME_FORMAT)) # also pass number of assignments for this leg (leg cannot be deleted if acnt > 0) jd['acnt'] = Assignment.objects.filter(leg=el).count()
log_message('FAILURE -- error message: ' + str(e), context['user']) # close mailbox connection -- marked messages will now be deleted mailbox.quit() except Exception, e: warn_user(context, 'Error while retrieving mail', 'System message: ' + str(e)) # add picture queue mail address context['pq_mail'] = settings.PICTURE_QUEUE_MAIL # add course data to context context['course'] = { 'object': c, 'hex': encode(c.id, context['user_session'].encoder), 'start': c.language.fdate(c.start_date), 'end': c.language.fdate(c.end_date), 'manager': prefixed_user_name(c.manager), 'instructors': ', '.join([prefixed_user_name(i) for i in c.instructors.all()]) } # add list of queue pictures for this course context['pictures'] = [{ 'object': qp, 'hex': encode(qp.id, day_code(PQ_DAY_CODE), True), # NOTE: deterministic encoding! 'time_received': qp.time_received.strftime('%H:%M'), 'sender': qp.mail_from_name if qp.mail_from_name else qp.mail_from_address } for qp in QueuePicture.objects.filter(course=c).order_by('time_received', 'id') ] context['page_title'] = 'Presto Picture Queue' return render(request, 'presto/picture_queue.html', context)
def set_history_properties(context, p): # switch to the course language lang = p.estafette.course.language # add the language-sensitive properties to the context context['lang'] = lang context['last_action'] = lang.ftime(p.student.last_action) context['start'] = lang.ftime(p.estafette.start_time) context['end'] = lang.ftime(p.estafette.end_time) context['desc'] = p.estafette.estafette.description context['next_deadline'] = p.estafette.next_deadline() context['decisions'] = [] # NOTE: first see whether participant has decided on appeals in this estafette ap_list = Appeal.objects.filter(referee__user=p.student.user ).filter(review__reviewer__estafette=p.estafette).exclude(time_decided=DEFAULT_DATE) if ap_list: # create a list of decided appeals for each course estafette ap_nr = 0 for ap in ap_list: ap_nr += 1 # add the appeal that has been decided ar = ap.review a = ar.assignment ap_dict = { 'nr': ap_nr, 'appeal': ap, 'appeal_title': '%s %s%s <span style="margin-left: 1em; font-weight: 500">(%s)</span>' % ( lang.phrase('Appeal_case_decision'), a.case.letter, str(a.leg.number), lang.ftime(ap.time_decided)), # show names only to instructors 'show_names': has_role(context, 'Instructor'), # add assignment properties 'case_title': '%s %s: %s' % (lang.phrase('Case'), a.case.letter, a.case.name), 'case_desc': a.case.description, 'step_title': '%s %s: %s' % (lang.phrase('Step'), a.leg.number, a.leg.name), 'step_desc': a.leg.description, 'author': a.participant.student.dummy_name(), 'time_uploaded': lang.ftime(a.time_uploaded), 'pred_file_list': a.leg.file_list, 'pred_hex': encode(a.id, context['user_session'].encoder), # add properties of the review that is appealed against 'review': ar, 'rev_items': ar.item_list(), 'reviewer': ar.reviewer.student.dummy_name(), 'time_reviewed': lang.ftime(ar.time_submitted), 'time_appealed': lang.ftime(ar.time_appraised), # add properties concerning the appeal (but attributes of the PeerReview object) 'imp_opinion': lang.phrase('Pred_opinion_succ_work') % lang.phrase(['Pass', 'No_improvement', 'Minor_changes', 'Good_job'][ar.improvement_appraisal]), 'time_assigned': lang.ftime(ar.time_appeal_assigned), # add appeal properties 'time_viewed': lang.ftime(ap.time_first_viewed), 'penalties_as_text': lang.penalties_as_text(ap.predecessor_penalty, ap.successor_penalty), 'hex': encode(ap.id, context['user_session'].encoder) } # pass hex for case if it has a file attached if a.case.upload: ap_dict['case_hex'] = encode(a.case.id, context['user_session'].encoder) # pass predecessor's appreciation of the decision (if submitted) if ap.time_acknowledged_by_predecessor != DEFAULT_DATE: ap_dict['time_pred_appr'] = lang.ftime(ap.time_acknowledged_by_predecessor) ap_dict['pred_appr_icon'] = FACES[ap.predecessor_appraisal] ap_dict['pred_appr_color'] = COLORS[ap.predecessor_appraisal] ap_dict['pred_motiv'] = ap.predecessor_motivation ap_dict['pred_objected'] = ap.is_contested_by_predecessor # pass predecessor's appreciation of the decision (if submitted) if ap.time_acknowledged_by_successor != DEFAULT_DATE: ap_dict['time_succ_appr'] = lang.ftime(ap.time_acknowledged_by_successor) ap_dict['succ_appr_icon'] = FACES[ap.successor_appraisal] ap_dict['succ_appr_color'] = COLORS[ap.successor_appraisal] ap_dict['succ_motiv'] = ap.successor_motivation ap_dict['succ_objected'] = ap.is_contested_by_successor context['decisions'].append(ap_dict) # now compile a consecutive list of all the student's actions in this estafette # (1) get list of all assignments for the student so far (except rejections and clones!) a_list = Assignment.objects.filter(participant=p).exclude(is_rejected=True ).filter(clone_of__isnull=True).order_by('leg__number') # from this list, derive a list with only the primary keys of the assignments aid_list = [a.id for a in a_list] # (2) get list of all reviews the student has written so far pr_given = PeerReview.objects.filter(reviewer=p).exclude(time_submitted=DEFAULT_DATE) # (3) also get list of all reviews the student has received so far pr_received = PeerReview.objects.filter(assignment__id__in=aid_list ).exclude(time_submitted=DEFAULT_DATE) # (4) also get the "oldest" user download objects for the assignment set ud_set = UserDownload.objects.filter(user=p.student.user, assignment__id__in=aid_list ).annotate(first_download_at=Min('time_downloaded')) # process the assignments in their number sequence steps = [] for a in a_list: # show all the student's assignments (i.e., also those without file uploads) step_nr = a.leg.number step = { 'step_nr': step_nr, 'assignment': a, 'header': lang.phrase('Step_header') % (step_nr, a.leg.name), # indicate whether the student did upload this step (or file list should not be shown) 'uploaded': (a.time_uploaded != DEFAULT_DATE), 'time': lang.ftime(a.time_uploaded), 'case_title': '%s %s: %s' % (lang.phrase('Case'), a.case.letter, a.case.name), 'case': a.case.description, 'desc': a.leg.description, 'Assigned_to_you': lang.phrase('Assigned_to_you') % lang.ftime(a.time_assigned), 'You_uploaded': lang.phrase('You_uploaded') % lang.ftime(a.time_uploaded), 'upl_items': a.item_list(), 'own_file_list': a.leg.file_list(), 'pred_file_list': [], 'succ_file_list': [], 'pred_reviews': [], 'succ_reviews': [], 'own_hex': encode(a.id, context['user_session'].encoder) } # pass hex for case if it has a file attached if a.case.upload: step['case_hex'] = encode(a.case.id, context['user_session'].encoder) # add half point bonus statement if such bonus was earned if a.on_time_for_bonus(): step['time_bonus'] = lang.phrase('Half_point_bonus') # for all but the first step, also show the review(s) the student has given on preceding steps if step_nr > 1: # the file list is step-specific, but not review-specific step['pred_file_list'] = a.predecessor.leg.file_list() step['pred_hex'] = encode(a.predecessor.id, context['user_session'].encoder) # get the first user download (if any) fud = ud_set.filter(assignment__id=a.id).first() if fud: step['downloaded'] = lang.ftime(fud.first_download_at) # the student may have given several reviews: rejections and second opinions prgl = pr_given.filter(assignment__leg__number=step_nr - 1).order_by('time_submitted') # add these reviews to this step for pr in prgl: step['pred_reviews'].append(given_review_dict(context, pr, lang)) # the student may also have received reviews of own work prrl = pr_received.filter(assignment__leg__number=step_nr).order_by('time_submitted') for pr in prrl: # do not show reviews that have been saved, but for which the successor did NOT # upload yet (unless it is a rejection or a FINAL review or a second opinion) if not (pr.is_rejection or pr.is_second_opinion or pr.final_review_index): # to prevent all error, double-check whether there IS a successor # NOTE: if reviewer is "instructor student", then skip the upload check if pr.assignment.successor and not (pr.reviewer.student.dummy_index < 0): if pr.assignment.successor.time_uploaded == DEFAULT_DATE: continue # skip this review, but continue looping r = { 'object': pr, 'time_submitted': lang.ftime(pr.time_submitted), 'rev_items': pr.item_list() # no 'pred_hex' field, as the reviewed work is the student's own step } # if student has responded, additional fields must be added if pr.time_appraised != DEFAULT_DATE: r['time_appraised'] = lang.ftime(pr.time_appraised) r['app_icon'] = FACES[pr.appraisal] r['app_header'] = lang.phrase('Your_appraisal_header') % lang.phrase(['ERROR', 'You_quite_happy', 'Mixed_feelings', 'You_unhappy'][pr.appraisal]) # student can see reviewer's work only if review is not a rejection, # a second opinion, or a final review (which has by definition no successor) if not (pr.is_rejection or pr.is_second_opinion or pr.final_review_index): # to prevent all error, double-check whether there IS a successor if pr.assignment.successor: step['succ_file_list'] = pr.assignment.successor.leg.file_list() step['succ_hex'] = encode(pr.assignment.successor.id, context['user_session'].encoder) r['imp_opinion'] = lang.phrase('Your_opinion_on_improvement') % lang.phrase(['Pass', 'No_improvement', 'Minor_changes', 'Good_job'][pr.improvement_appraisal]) # in case of an appeal, report the appeal status if pr.is_appeal: if pr.time_appeal_assigned != DEFAULT_DATE: r['time_assigned'] = lang.ftime(pr.time_appeal_assigned) ap = Appeal.objects.filter(review=pr).first() if ap: r['referee'] = prefixed_user_name(ap.referee.user) if ap.time_first_viewed != DEFAULT_DATE: r['time_first_viewed'] = lang.ftime(ap.time_first_viewed) if ap.time_decided != DEFAULT_DATE: r['time_decided'] = lang.ftime(ap.time_decided) r['ref_rat'] = ap.grade r['ref_motiv'] = ap.grade_motivation r['penalties'] = lang.penalties_as_text( ap.predecessor_penalty, ap.successor_penalty) # NOTE: in this case, the student ("you") is the predecessor ... if ap.time_acknowledged_by_predecessor != DEFAULT_DATE: r['time_your_appr'] = lang.ftime(ap.time_acknowledged_by_predecessor) r['your_appr_icon'] = FACES[ap.predecessor_appraisal] r['your_motiv'] = ap.predecessor_motivation r['you_objected'] = ap.is_contested_by_predecessor # ... and hence the other party the successor if ap.time_acknowledged_by_successor != DEFAULT_DATE: r['time_other_appr'] = lang.ftime(ap.time_acknowledged_by_successor) r['other_appr_icon'] = FACES[ap.successor_appraisal] r['other_motiv'] = ap.successor_motivation r['other_objected'] = ap.is_contested_by_successor # add the review to the list step['succ_reviews'].append(r) # now all step properties have been set, so the step is added to the list steps.append(step) # and finally the step list is added to the context context['steps'] = steps # the student may also have given final reviews context['final_reviews'] = [] prgl = pr_given.filter(final_review_index__gt=0).order_by('final_review_index') # add these reviews to this step n = 0 for pr in prgl: r = given_review_dict(context, pr, lang) n += 1 r['nr'] = n a = pr.assignment r['header'] = lang.phrase('Final_review_header') % (step_nr, a.leg.name) r['case_title'] = '%s %s: %s' % (lang.phrase('Case'), a.case.letter, a.case.name) r['case'] = a.case.description # pass hex for case if it has a file attached if a.case.upload: r['case_hex'] = encode(a.case.id, context['user_session'].encoder) context['final_reviews'].append(r)
def instructor(request, **kwargs): context = generic_context(request) # check whether user can have instructor role if not change_role(context, 'Instructor'): return render(request, 'presto/forbidden.html', context) # create list of courses in which the user is manager/instructor context['courses'] = [] c_set = Course.objects.filter( Q(instructors=context['user']) | Q(manager=context['user'])).distinct() for c in c_set: context['courses'].append({ 'object': c, 'start': c.start_date.strftime(DATE_FORMAT), 'end': c.end_date.strftime(DATE_FORMAT), 'manager': prefixed_user_name(c.manager), 'estafette_count': CourseEstafette.objects.filter(course=c).count(), 'hex': encode(c.id, context['user_session'].encoder) }) # create list of estafettes of which the user is creator/editor context['estafettes'] = [{ 'object': e, 'edits': EDIT_STRING % (prefixed_user_name(e.last_editor), timezone.localtime( e.time_last_edit).strftime(DATE_TIME_FORMAT)), 'template': e.template.name, 'case_count': EstafetteCase.objects.filter(estafette=e).count(), 'hex': encode(e.id, context['user_session'].encoder) } for e in Estafette.objects.filter( Q(editors=context['user']) | Q(creator=context['user'])).distinct()] # create list of active project relays in which the user is instructor context['running_relays'] = [{ 'object': ce, 'start_time': c.language.ftime(ce.start_time), 'end_time': c.language.ftime(ce.end_time), 'next_deadline': ce.next_deadline(), 'participant_count': Participant.objects.filter(estafette=ce, student__dummy_index__gt=-1).count(), 'active_count': Participant.objects.filter(estafette=ce, student__dummy_index__gt=-1).filter( time_last_action__gte=timezone.now() - timedelta(days=1)).count(), 'demo_code': ce.demonstration_code(), 'hex': encode(ce.id, context['user_session'].encoder) } for ce in CourseEstafette.objects.filter( course__in=c_set, is_deleted=False).exclude( start_time__gte=timezone.now()).exclude( end_time__lte=timezone.now())] # create list of estafette templates that the user can choose from context['templates'] = [{ 'object': et, 'hex': encode(et.id, context['user_session'].encoder) } for et in EstafetteTemplate.objects.filter(published=True)] context['page_title'] = 'Presto Instructor' return render(request, 'presto/instructor.html', context)
try: h = kwargs.get('hex', '') context = generic_context(request, h) etid = decode(h, context['user_session'].decoder) et = EstafetteTemplate.objects.get(pk=etid) log_message('Deleting template %s' % et.name, context['user']) et.delete() except Exception, e: report_error(request, context, e) return render(request, 'presto/error.html', context) # pass list of estafette templates that the user is (co-)authoring context['templates'] = [{ 'object': t, 'hex': encode(t.id, context['user_session'].encoder), 'edits': EDIT_STRING % (prefixed_user_name(t.last_editor), timezone.localtime(t.time_last_edit).strftime(DATE_TIME_FORMAT)), 'leg_count': EstafetteLeg.objects.filter(template=t).count() } for t in EstafetteTemplate.objects.filter( Q(editors=context['user']) | Q(creator=context['user'])).distinct() ] # TO DO: also pass list of questionnaire templates that the user is (co-)authoring context['questionnaires'] = [{ 'object': q, 'hex': encode(q.id, context['user_session'].encoder), 'edits': EDIT_STRING % (prefixed_user_name(q.last_editor), timezone.localtime(q.time_last_edit).strftime(DATE_TIME_FORMAT)), 'item_count': 0, # to be changed!! } for q in QuestionnaireTemplate.objects.filter( Q(editors=context['user']) | Q(creator=context['user'])).distinct()
ap.grade = int(request.POST.get('dr', 0)) ap.grade_motivation = request.POST.get('dm', '') # print ap.grade_motivation ap.predecessor_penalty = float(request.POST.get('pp', 0)) ap.successor_penalty = float(request.POST.get('sp', 0)) # NOTE: time_decided is not set => saved, but not submitted yet ap.save() jd['pt'] = ap.review.reviewer.student.course.language.penalties_as_text( ap.predecessor_penalty, ap.successor_penalty) log_message('Appeal decision saved: ' + unicode(ap), presto_user) elif a == 'get estafette': eid = decode(request.POST.get('h', ''), user_session.encoder) e = Estafette.objects.get(pk=eid) jd['n'] = e.name jd['d'] = e.description jd['e'] = EDIT_STRING % (prefixed_user_name(e.last_editor), timezone.localtime(e.time_last_edit).strftime(DATE_TIME_FORMAT)) # also pass number of estafettes having this template (no deletion if cnt > 0) jd['cnt'] = CourseEstafette.objects.filter(estafette=e).count() elif a == 'get case': ecid = decode(request.POST.get('h', ''), user_session.encoder) ec = EstafetteCase.objects.get(pk=ecid) jd['n'] = ec.name jd['d'] = ec.description jd['k'] = ec.required_keywords jd['u'] = '' if ec.upload == None else ec.upload.original_name jd['e'] = EDIT_STRING % (prefixed_user_name(ec.last_editor), timezone.localtime(ec.time_last_edit).strftime(DATE_TIME_FORMAT)) # also pass number of assignments having this case (case cannot be deleted if acnt > 0) jd['acnt'] = Assignment.objects.filter(case=ec).count() elif a == 'new estafette':
ce.save() log_message('Added new estafette to course', context['user']) except Exception, e: report_error(context, e) return render(request, 'presto/error.html', context) # add course properties that need conversion to context context['course'] = { 'object': c, 'start': c.language.fdate(c.start_date), 'end': c.language.fdate(c.end_date), 'manager': prefixed_user_name(c.manager), 'owned': c.manager == context['user'], 'instructors': [{ 'name': prefixed_user_name(i), 'hex': encode(i.id, context['user_session'].encoder) } for i in c.instructors.all()], 'hex': encode(c.id, context['user_session'].encoder), 'disc': (c.badge_color >> 24) & 15, 'red': (c.badge_color >> 16) & 255, 'green': (c.badge_color >> 8) & 255, 'blue': c.badge_color & 255 }
def verify_certified_image(img): try: # make pixels accessible as pix[x, y] pix = img.load() w, h = img.size # check for approriate dimensions if h != BADGE_HEIGHT or w != BADGE_WIDTH: raise ValueError('Badge should be 256x256 pixels') # get the 256-bit signature signature = ''.join([test_bit(pix, i) for i in range(0, 256)]) # get the length of the "payload" coded in the next 14 bits bits = ''.join([test_bit(pix, i) for i in range(256, 270)]) bit_count = int(bits, 2) if bit_count > MAX_DATA_BITS: raise ValueError('Invalid data size (%d)' % bit_count) # get the actual data bits = ''.join([test_bit(pix, i) for i in range(270, 270 + bit_count)]) # check integrity of bits bits_hash = hash_to_binary(hexlify(pbkdf2_hmac("sha256", bits, settings.BADGE_SALT, settings.BADGE_ITERATIONS))) if bits_hash != signature: raise ValueError('Corrupted badge data') # decode the bits bd = binary_to_dict(bits) # see if the standard badge properties exist mf = list(set(['ID', 'CC', 'CN', 'AL', 'TI', 'PR', 'FN', 'EM']) - set(bd.keys())) if mf: raise ValueError('Incomplete data (missing: %s)' % ', '.join(mf)) # see if the badge exists in the database # NOTE: use filter instead of get so that we can generate our own error message b = PrestoBadge.objects.filter(pk=bd['ID']) if b.count() == 0: raise ValueError('Unmatched badge ID') # get the first element (should be the only one) b = b.first() if b.participant: u = b.participant.student.user pr = b.participant.estafette.estafette.name if b.referee: u = b.referee.user pr = b.referee.estafette_leg.template.name # see if badge data match those in database if prefixed_user_name(u) != bd['FN']: raise ValueError('Holder name (%s) does not match "%s"' % (bd['FN'], prefixed_user_name(u))) if u.email != bd['EM']: raise ValueError('Holder e-mail address (%s) does not match "%s"' % (bd['EM'], u.email)) if b.course.code != bd['CC']: raise ValueError('Course code (%s) does not match "%s"' % (bd['CC'], b.course.code)) if b.course.name != bd['CN']: raise ValueError('Course name (%s) does not match "%s"' % (bd['CN'], b.course.name)) if pr != bd['PR']: raise ValueError('Project relay name (%s) does not match "%s"' % (bd['PR'], pr)) if b.attained_level != bd['AL']: raise ValueError('Attained level (%d) should have been %d' % (bd['AL'], b.attained_level)) # update badge verification parameters b.time_last_verified = timezone.now() b.verification_count += 1 b.save() # return the badge object return b except Exception, e: log_message('Failed to validate badge: ' + str(e)) return False
letter=l, creator=context['user'], time_created=timezone.now(), last_editor=context['user'], time_last_edit=timezone.now()) # when no errors, create list of cases in this estafette ec_list = EstafetteCase.objects.filter(estafette=e) e_cases = [] for ec in ec_list: e_cases.append({ 'object': ec, 'edits': EDIT_STRING % (prefixed_user_name(ec.last_editor), timezone.localtime(ec.time_last_edit).strftime(DATE_TIME_FORMAT)), 'hex': encode(ec.id, context['user_session'].encoder) }) context['estafette'] = { 'object': e, 'edits': EDIT_STRING % (prefixed_user_name(e.last_editor), timezone.localtime( e.time_last_edit).strftime(DATE_TIME_FORMAT)), 'hex': encode(e.id, context['user_session'].encoder), 'owner': prefixed_user_name(e.creator),