Esempio n. 1
0
def badge(request, **kwargs):
    # NOTE: a downloaded image is part the current page, meaning that the coding keys
    # should NOT be rotated; this is achieved by passing "NOT" as test code.
    context = generic_context(request, 'NOT')
    try:
        # check whether user can have student role
        if not (has_role(context, 'Student') or has_role(context, 'Instructor')):
            raise Exception('No access')

        # if a sample badge is asked for, render it in the requested color
        bc = kwargs.get('bc', '')
        if bc:
            # render a participant badge (image only, not certified)
            return render_sample_badge(int(bc))
        
        # otherwise, a hex-encoded badge ID is needed
        h = kwargs.get('hex', '')
        # verify that hex code is valid
        # NOTE: since keys have not been rotated, use the ENcoder here!
        bid = decode(h, context['user_session'].encoder)
        b = PrestoBadge.objects.get(pk=bid)
        # for speed, the reward gallery requests tiny images (80x80 px)
        if kwargs.get('tiny', ''):
            return render_tiny_badge_image(b)
        # if otherwise render the certified badge image
        return render_certified_badge(b)
    
    except Exception, e:
        log_message('ERROR while rendering badge: %s' % str(e), context['user'])
        with open(os.path.join(settings.IMAGE_DIR, 'not-found.png'), "rb") as f:
            return HttpResponse(f.read(), content_type="image/png")
Esempio n. 2
0
def suspect(request, **kwargs):
    h = kwargs.get('hex', '')
    context = generic_context(request, h)
    try:
        if not has_role(context, 'Administrator'):
            raise IOError('No permission to download relay data set')
        context['page_title'] = 'Presto Plagiarism Report'
        ceid = decode(h, context['user_session'].decoder)
        # get all relay assignments (except clones and dummy students) having
        # a scan result of 5% or more, and for steps 3 and above 3% or more
        a_set = Assignment.objects.filter(clone_of__isnull=True,
            participant__estafette__id=ceid, participant__student__dummy_index=0,
            time_scanned__gt=DEFAULT_DATE, scan_result__gte=3
            ).select_related('case', 'leg', 'participant__student'
            ).exclude(leg__number__lte=2, scan_result__lte=5
            ).order_by('case__letter', 'leg__number', 'time_uploaded')
        
        content = '%d suspects\n\n' % len(a_set)
        for a in a_set:
            content += '\n%s%d by %s (#%d, %d%%)' % (a.case.letter, a.leg.number,
                a.participant.student.dummy_name(), a.id, a.scan_result)
            pr_a = a.predecessor
            if pr_a in a_set:
                content += ' -- builds on %s%d by %s (#%d)' % (pr_a.case.letter,
                    pr_a.leg.number, pr_a.participant.student.dummy_name(), pr_a.id)
         

        return HttpResponse(content, content_type='text/plain; charset=utf-8')

    except IOError, e:
        report_error(context, e) 
        return render(request, 'presto/error.html', context)
Esempio n. 3
0
def dataset(request, **kwargs):
    h = kwargs.get('hex', '')
    context = generic_context(request, h)
    try:
        if not has_role(context, 'Administrator'):
            raise IOError('No permission to download relay data set')
        context['page_title'] = 'Presto Relay Dataset'
        context['no_names'] = True
        context['identify'] = True
        ceid = decode(h, context['user_session'].decoder)
        # get all relay participants except dummy students
        p_set = Participant.objects.filter(estafette__id=ceid, student__dummy_index=0)
        # create list of ID - e-mail address
        data = '\n'.join(['#(%d)\t%s' % (v[0], v[1])
            for v in p_set.values_list('id', 'student__user__email')])
        # make a temporary zip file
        with tempfile.SpooledTemporaryFile() as tmp:
            with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as zip_file:
                # add a text file with (id - e-mail address) for each participant
                zip_file.writestr('participants.txt', data.encode('utf-8'))
                for p in p_set:
                    # add data for progress bar
                    context['things_to_do'] = p.things_to_do()
                    # NOTE: context properties will include identities of participants
                    set_history_properties(context, p)
                    context['object'] = p
                    context['base_url'] = '.'
                    data = render_to_string('estafette_history.html', context)
                    data = data.replace('/static/presto', 'presto')
                    zip_file.writestr('p%d.html' % p.id, data.encode('utf-8'))
            # reset file pointer
            tmp.seek(0)
            response = HttpResponse(tmp.read(), content_type='application/zip')
            response['Content-Disposition'] = 'attachment; filename="relay_dataset.zip"'
            return response

    except IOError, e:
        report_error(context, e) 
        return render(request, 'presto/error.html', context)
Esempio n. 4
0
def enroll(request, **kwargs):
    context = generic_context(request)
    print context
    # check whether user can have student role
    if not change_role(context, 'Student'):
        return render(request, 'presto/forbidden.html', context)
    # check whether user is enrolling
    cc = kwargs.get('course', '')
    cl = Course.objects.filter(code=cc)
    if not cl:
        warn_user(
            context, 'Unknown course',
            'Course code "<tt>%s</tt>" is not recognized. Please check with your faculty staff.'
            % cc)
    elif cl.first().is_hidden:
        warn_user(context, 'Unknown course',
                  'Course %s is not open for enrollment.' % cl.first().title)
    else:
        c = cl.first()
        context['course'] = {
            'object': c,
            'hex': encode(c.id, context['user_session'].encoder)
        }
        # switch to the course language
        context['lang'] = c.language
        csl = CourseStudent.objects.filter(user=context['user'], course=c)
        if csl and not (has_role(context, 'Instructor')
                        or is_demo_user(context)):
            warn_user(
                context, c.language.phrase('Already_enrolled'),
                c.language.phrase('Enrolled_on') %
                (c.title(), c.language.ftime(csl.first().time_enrolled)))
        else:
            context['enroll'] = c.language.phrase(
                'About_to_enroll') % c.title()

    context['page_title'] = 'Presto Enrollment'
    return render(request, 'presto/enroll.html', context)
Esempio n. 5
0
def log_file(request, **kwargs):
    ymd = kwargs.get('date', '')
    if ymd == '':
        ymd = timezone.now().strftime('%Y%m%d')
    context = generic_context(request)
    try:
        log_message('Viewing log file %s' % ymd, context['user'])
        if not has_role(context, 'Administrator'):
            raise IOError('No permission to view log files')
        path = os.path.join(settings.LOG_DIR, 'presto-%s.log' % ymd)
        with codecs.open(path, 'r', encoding='utf8') as log:
            content = log.read()
            lines = kwargs.get('lines', '')
            pattern = unquote(kwargs.get('pattern', '')).decode('utf8') 
            if lines:
                # show last N lines
                content = '\n'.join(content.split('\n')[int(lines):])
            elif pattern:
                # show pattern-matching lines, separated by blank line
                content = '\n\n'.join(re.findall('^.*' + pattern + '.*$', content, re.MULTILINE))
    except IOError, e:
        report_error(context, e) 
        return render(request, 'presto/error.html', context)
Esempio n. 6
0
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)
Esempio n. 7
0
def progress(request, **kwargs):
    # NOTE: a downloaded image is part the current page, meaning that the coding keys
    # should NOT be rotated; this is achieved by passing "NOT" as test code.
    context = generic_context(request, 'NOT')
    try:
        # check whether user can have student role
        if not (has_role(context, 'Student')
                or has_role(context, 'Instructor')):
            raise Exception('No access')
        h = kwargs.get('hex', '')
        # verify that hex code is valid
        # NOTE: since keys have not been rotated, use the ENcoder here!
        oid = decode(h, context['user_session'].encoder)
        # check whether oid indeed refers to an existing participant or course estafette
        p_or_ce = kwargs.get('p_or_ce', '')
        if p_or_ce == 'p':
            p = Participant.objects.get(pk=oid)
            ce = p.estafette
        else:
            p = None
            ce = CourseEstafette.objects.get(pk=oid)

        # get the basic bar chart
        img = update_progress_chart(ce)

        # if image requested by a participant, add orange markers for his/her uploads
        if p:
            draw = ImageDraw.Draw(img)
            # get a font (merely to draw nicely anti-aliased circular outlines)
            fnt = ImageFont.truetype(
                os.path.join(settings.FONT_DIR, 'segoeui.ttf'), 25)
            # calculate how many seconds of estafette time is represented by one bar
            time_step = int(
                (ce.end_time - ce.start_time).total_seconds() / BAR_CNT) + 1
            # get the number of registered participants (basis for 100%)
            p_count = Participant.objects.filter(estafette=ce).count()
            # get leg number and upload time all uploaded assignments for this participant
            a_list = Assignment.objects.filter(participant=p).filter(
                time_uploaded__gt=DEFAULT_DATE).filter(
                    clone_of__isnull=True).values('leg__number',
                                                  'time_uploaded')
            for a in a_list:
                # get the number of assignments submitted earlier
                cnt = Assignment.objects.filter(
                    participant__estafette=ce
                ).filter(leg__number=a['leg__number']).filter(
                    time_uploaded__gt=DEFAULT_DATE).filter(
                        clone_of__isnull=True).exclude(
                            time_uploaded__gt=a['time_uploaded']).count()
                bar = int(
                    (a['time_uploaded'] - ce.start_time).total_seconds() /
                    time_step)
                perc = round(250 * cnt / p_count)
                x = V_AXIS_X + bar * BAR_WIDTH
                y = H_AXIS_Y - perc - 5
                # mark uploads with orange & white outline (10 pixels diameter)
                draw.ellipse([x, y, x + 10, y + 10],
                             fill=(236, 127, 44, 255),
                             outline=None)
                # draw white letter o to produce neat circular outline
                draw.text((x - 1.5, y - 14.5),
                          'o',
                          font=fnt,
                          fill=(255, 255, 255, 255))
            # get nr and submission time for this participant's final reviews
            nr_of_steps = ce.estafette.template.nr_of_legs()
            r_set = PeerReview.objects.filter(reviewer=p).filter(
                assignment__leg__number=nr_of_steps).filter(
                    time_submitted__gt=DEFAULT_DATE).values(
                        'reviewer__id',
                        'time_submitted').order_by('reviewer__id',
                                                   'time_submitted')
            r_index = 0
            for r in r_set:
                r_index += 1
                # get the number of final reviews submitted earlier
                cnt = PeerReview.objects.filter(reviewer__estafette=ce).filter(
                    assignment__leg__number=nr_of_steps).filter(
                        time_submitted__gt=DEFAULT_DATE).exclude(
                            time_submitted__gt=r['time_submitted']).values(
                                'reviewer__id', 'time_submitted').order_by(
                                    'reviewer__id', 'time_submitted').annotate(
                                        rev_cnt=Count('reviewer_id')).filter(
                                            rev_cnt=r_index).count()
                bar = int(
                    (r['time_submitted'] - ce.start_time).total_seconds() /
                    time_step)
                perc = round(250 * cnt / p_count)
                x = V_AXIS_X + bar * BAR_WIDTH
                y = H_AXIS_Y - perc - 5
                # mark final reviews with orange
                draw.ellipse([x, y, x + 10, y + 10],
                             fill=(236, 127, 44, 255),
                             outline=None)
                # draw black letter o to produce neat circular outline
                draw.text((x - 1.5, y - 14.5),
                          'o',
                          font=fnt,
                          fill=(0, 0, 0, 255))
        # output image to browser (do NOT save it as a file)
        response = HttpResponse(content_type='image/png')
        img.save(response, 'PNG')
        return response

    except Exception, e:
        log_message('ERROR while generating progress chart: %s' % str(e),
                    context['user'])
        with open(os.path.join(settings.IMAGE_DIR, 'not-found.png'),
                  "rb") as f:
            return HttpResponse(f.read(), content_type="image/png")
Esempio n. 8
0
def ack_letter(request, **kwargs):
    # NOTE: downloading a file opens a NEW browser tab/window, meaning that
    # the coding keys should NOT be rotated; this is achieved by passing "NOT" as test code.
    context = generic_context(request, 'NOT')
    # check whether user can have student role
    if not has_role(context, 'Student'):
        return render(request, 'presto/forbidden.html', context)
    try:
        h = kwargs.get('hex', '')
        # verify that letter exists
        # NOTE: since keys have not been rotated, use the ENcoder here!
        lid = decode(h, context['user_session'].encoder)
        # get letter properties
        loa = LetterOfAcknowledgement.objects.get(id=lid)
        # update fields, but do not save yet because errors may still prevent effective rendering
        loa.time_last_rendered = timezone.now()
        loa.rendering_count += 1
        # get the dict with relevant LoA properties in user-readable form
        rd = loa.as_dict()
        # create letter as PDF
        pdf = MyFPDF()
        pdf.add_font('DejaVu', '', DEJAVU_FONT, uni=True)
        pdf.add_font('DejaVu', 'I', DEJAVU_OBLIQUE_FONT, uni=True)
        pdf.add_font('DejaVu', 'B', DEJAVU_BOLD_FONT, uni=True)
        pdf.add_font('DejaVu', 'BI', DEJAVU_BOLD_OBLIQUE_FONT, uni=True)
        pdf.add_page()
        # see whether course has a description; if so, make a reference to page 2 and
        # and prepare the text for this page 2
        if rd['CD']:
            see_page_2 = ' described on page 2'
        else:
            see_page_2 = ''
        # NOTE: if the RID entry (= the referee ID) equals zero, the letter is a participant LoA!
        if rd['RID'] == 0:
            pdf.letter_head(rd['AC'], rd['DI'],
                            'Acknowledgement of Project Relay completion')
            # add the participant acknowledgement text to the letter
            text = ''.join([
                'To whom it may concern,\n\n',
                'With this letter, DelftX, an on-line learning initiative of Delft University of',
                ' Technology through edX, congratulates ', rd['FN'],
                '* for having completed', ' the project relay ', rd['PR'],
                ' offered as part of the online course ', rd['CN'], see_page_2,
                '.\n\n',
                'A project relay comprises a series of steps: assignments that follow on from',
                ' each other. In each step, participants must first peer review, appraise, and',
                ' then build on the preceding step submitted by another participant.\n\n',
                'The project relay ', rd['PR'], ' comprised ', rd['SL'],
                ', where each step posed an intellectual challenge that will have required',
                ' several hours of work. DelftX appreciates in particular the contribution that',
                ' participants make to the learning of other participants by giving feedback',
                ' on their work.\n\n\n', rd['SN'], '\n', rd['SP']
            ])
        else:
            pdf.letter_head(rd['AC'], rd['DI'],
                            'Project Relay Referee Letter of Acknowledgement')
            # adapt some text fragments to attribute values
            cases = plural_s(rd['ACC'], 'appeal case')
            hours = plural_s(rd['XH'], 'hour')
            # average appreciation is scaled between -1 and 1
            if rd['AA'] > 0:
                appr = ' The participants involved in the appeal were appreciative of the arbitration.'
            elif rd['AA'] < -0.5:
                appr = ' Regrettably, the participants involved in the appeal were generally not appreciative of the arbitration.'
            else:
                appr = ''
            if rd['DFC'] == rd['DLC']:
                period = 'On ' + rd['DLC']
            else:
                period = 'In the period between %s and %s' % (rd['DFC'],
                                                              rd['DLC'])
            # add the referee acknowledgement text to the letter
            text = ''.join([
                'To whom it may concern,\n\n',
                'With this letter, DelftX, an on-line learning initiative of Delft University of Technology',
                ' through edX, wishes to express its gratitude for the additional efforts made by ',
                rd['FN'], '* while participating in the project relay ',
                rd['PR'], ' offered as part of the online course ', rd['CN'],
                see_page_2, '.\n\n',
                'A project relay comprises a series of steps: assignments that follow on from each other. ',
                'In each step, participants must first peer review, appraise, and then build on the ',
                'preceding step submitted by another participant. Participant ',
                rd['FN'],
                ' has not only completed the course, but also passed the referee test for ',
                rd['SL'], ' of the ', rd['PR'],
                ' project relay. This implies having a better command of the subject',
                ' taught than regular participants.\n\n',
                'Referees arbitrate appeal cases, i.e., situations where the reviewed participant ',
                'disagrees with the reviewer\'s critique and/or appraisal. ',
                period, ', participant ', rd['FN'], ' has arbitrated on ',
                cases, '. This corresponds to approximately ', hours,
                ' of work.', appr,
                '\n\nThe role of referee is indispensable to run project ',
                'relays on a large scale. DelftX therefore greatly values participants volunteering to ',
                'act as such, since it requires significant effort on top of the regular assignments.\n\n\n',
                rd['SN'], '\n', rd['SP']
            ])
        pdf.main_text(text)
        # add footnote with disclaimer
        pdf.footnote(rd['EM'])
        if see_page_2:
            pdf.page_2(rd)
        # set document properties
        if rd['RID'] == 0:
            task = 'completing a project relay'
        else:
            task = 'work as project relay referee'
        pdf.set_properties(rd['AC'], task, rd['FN'], rd['RC'], rd['TLR'])
        # output to temporary file
        temp_file = mkstemp()[1]
        pdf.output(temp_file, 'F')
        log_message('Rendering acknowledgement letter for %s' % rd['PR'],
                    context['user'])
        # push the PDF as attachment to the browser
        w = FileWrapper(file(temp_file, 'rb'))
        response = HttpResponse(w, content_type='application/pdf')
        response[
            'Content-Disposition'] = 'attachment; filename="presto-LoA.pdf"'
        # now we can assume that the PDF will appear, so the updated letter data can be saved
        loa.save()
        return response
    except Exception, e:
        report_error(context, e)
        return render(request, 'presto/error.html', context)
Esempio n. 9
0
def download(request, **kwargs):
    # NOTE: downloading a file opens a NEW browser tab/window, meaning that
    # the coding keys should NOT be rotated; this is achieved by passing "NOT" as test code.
    context = generic_context(request, 'NOT')
    # check whether user can have student or instructor role
    is_instructor = has_role(context, 'Instructor')
    if not (has_role(context, 'Student') or is_instructor):
        return render(request, 'presto/forbidden.html', context)
    try:
        h = kwargs.get('hex', '')
        # verify hex key
        # NOTE: since keys have not been rotated, use the ENcoder here!
        aid = decode(h, context['user_session'].encoder)
        file_name = kwargs.get('file_name', '')
        # file_name = 'case' indicates a download request for a case attachment
        if file_name == 'case':
            ec = EstafetteCase.objects.get(pk=aid)
            if ec.upload == None:
                raise ValueError('No attachment file for this case')
            f = ec.upload.upload_file
            ext = os.path.splitext(f.name)[1]
            w = FileWrapper(file(f.path, 'rb'))
            response = HttpResponse(w, 'application/octet-stream')
            response['Content-Disposition'] = (
                'attachment; filename="attachment-case-%s%s"' %
                (ec.letter, ext))
            return response

        # no case attachment? then the download request must concern an assignment
        work = kwargs.get('work', '')
        dwnldr = kwargs.get('dwnldr', '')
        # verify that download is for an existing assignment
        log_message('Looking for assignment #%d' % aid, context['user'])
        a = Assignment.objects.get(pk=aid)

        # get the list of participant uploads for this assignment (or its clone original)
        # and also the full path to the upload directory
        if a.clone_of:
            original = a.clone_of
            # in case a clone was cloned, keep looking until the "true" original has been found
            while original.clone_of:
                original = original.clone_of
            pul = ParticipantUpload.objects.filter(assignment=original)
            upl_dir = os.path.join(settings.MEDIA_ROOT,
                                   original.participant.upload_dir)
        else:
            pul = ParticipantUpload.objects.filter(assignment=a)
            upl_dir = os.path.join(settings.MEDIA_ROOT,
                                   a.participant.upload_dir)
        log_message('Upload dir = ' + upl_dir, context['user'])
        # create an empty temporary dir to hold copies of uploaded files
        temp_dir = os.path.join(upl_dir, 'temp')
        try:
            rmtree(temp_dir)
        except:
            pass
        os.mkdir(temp_dir)
        log_message('TEMP dir: ' + temp_dir, context['user'])
        if file_name == 'all-zipped':
            pr_work = 'pr-step%d%s' % (a.leg.number, a.case.letter)
            zip_dir = os.path.join(temp_dir, pr_work)
            os.mkdir(zip_dir)
            # copy the upladed files to the temporary dir ...
            for pu in pul:
                real_name = os.path.join(upl_dir,
                                         os.path.basename(pu.upload_file.name))
                # ... under their formal name, not their actual
                ext = os.path.splitext(pu.upload_file.name)[1].lower()
                formal_name = os_path(
                    os.path.join(zip_dir, pu.file_name) + ext)
                if is_instructor:
                    log_message(
                        'Copying %s "as is" to ZIP as %s' %
                        (real_name, formal_name), context['user'])
                    # NOTE: for instructors, do NOT anonymize the document
                    copy2(real_name, formal_name)
                else:
                    log_message(
                        'Copy-cleaning %s to ZIP as %s' %
                        (real_name, formal_name), context['user'])
                    # strip author data from file and write it to the "work" dir
                    clear_metadata(real_name, formal_name)
            # compress the files into a single zip file
            zip_file = make_archive(zip_dir, 'zip', temp_dir, pr_work)
            response = HttpResponse(FileWrapper(file(zip_file, 'rb')),
                                    content_type='application/zip')
            response['Content-Disposition'] = (
                'attachment; filename="%s.zip"' % pr_work)
            # always record download in database
            UserDownload.objects.create(user=context['user_session'].user,
                                        assignment=a)
            # only change time_first_download if it concerns a predecessor's work!
            if work == 'pre' and a.time_first_download == DEFAULT_DATE:
                a.time_first_download = timezone.now()
            a.time_last_download = timezone.now()
            a.save()
            return response
        else:
            # check whether file name is on "required files" list
            fl = a.leg.file_list()
            rf = False
            for f in fl:
                if f['name'] == file_name:
                    rf = f
            if not rf:
                raise ValueError('Unknown file name: %s' % file_name)
            # find the corresponding upload
            pul = pul.filter(file_name=rf['name'])
            if not pul:
                raise ValueError('File "%s" not found' % rf['name'])
            pu = pul.first()
            # the real file name should not be known to the user
            real_name = os.path.join(upl_dir,
                                     os.path.basename(pu.upload_file.name))
            ext = os.path.splitext(pu.upload_file.name)[1]
            # the formal name is the requested file field plus the document's extension
            formal_name = os_path(os.path.join(temp_dir, pu.file_name) + ext)
            if is_instructor:
                log_message(
                    'Copying %s "as is" to ZIP as %s' %
                    (real_name, formal_name), context['user'])
                # NOTE: for instructors, do NOT anonymize the document
                copy2(real_name, formal_name)
            else:
                # strip author data from the file
                log_message(
                    'Copy-cleaning %s to %s' % (real_name, formal_name),
                    context['user'])
                clear_metadata(real_name, formal_name)
            mime = {
                '.pdf':
                'application/pdf',
                '.docx':
                'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                '.xlsx':
                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                '.pptx':
                'application/vnd.openxmlformats-officedocument.presentationml.presentation'
            }
            w = FileWrapper(file(settings.LEADING_SLASH + formal_name, 'rb'))
            response = HttpResponse(w, content_type=mime[ext])
            response['Content-Disposition'] = (
                'attachment; filename="%s-%d%s%s"' %
                (file_name, a.leg.number, a.case.letter, ext))
            # always record download in database
            UserDownload.objects.create(user=context['user_session'].user,
                                        assignment=a)
            # only change time_first_download if it concerns a predecessor's work!
            if work == 'pre' and a.time_first_download == DEFAULT_DATE:
                a.time_first_download = timezone.now()
            a.time_last_download = timezone.now()
            a.save()
            # if work is downloaded for the first time by a referee, this should be registered
            if dwnldr == 'ref':
                ap = Appeal.objects.filter(review__assignment=a).first()
                if not ap:
                    raise ValueError('Appeal not found')
                if ap.time_first_viewed == DEFAULT_DATE:
                    ap.time_first_viewed = timezone.now()
                    ap.save()
                    log_message('First view by referee: ' + unicode(ap),
                                context['user'])
            return response

    except Exception, e:
        report_error(context, e)
        return render(request, 'presto/error.html', context)