예제 #1
0
파일: badge.py 프로젝트: pwgbots/presto
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")
예제 #2
0
파일: views.py 프로젝트: pwgbots/presto
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)
예제 #3
0
def administrator(request):
    context = generic_context(request)
    # check whether user can have administrator role
    if not change_role(context, 'Administrator'):
        return render(request, 'presto/forbidden.html', context)
    # show the administrator page
    context['page_title'] = 'Presto Administrator'
    return render(request, 'presto/administrator.html', context)
예제 #4
0
파일: developer.py 프로젝트: pwgbots/presto
def developer(request, **kwargs):
    context = generic_context(request)
    # check whether user can have developer role
    if not change_role(context, 'Developer'):
        return render(request, 'presto/forbidden.html', context)

    # check whether a template must be deleted
    if kwargs.get('action', '') == 'delete-template':
        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)
예제 #5
0
파일: views.py 프로젝트: pwgbots/presto
def index(request):
    context = generic_context(request)
    r = context['user_session'].active_role
    if r:
        if r.name == 'Student':
            return student(request)
        elif r.name == 'Instructor':
            return instructor(request)
        elif r.name == 'Developer':
            return developer(request)
        elif r.name == 'Administrator':
            return administrator(request)
    else:
        return render(request, 'presto/index.html', context)
예제 #6
0
def picture_queue(request, **kwargs):
    h = kwargs.get('hex', '')
    act = kwargs.get('action', '')
    # check whether user can view this course
    try:
        if act in ['delete', 'get']:
            # NOTE: when getting a picture, the coding keys should NOT be rotated
            context = generic_context(request, 'NOT')
            # and the day code should be used to decode the hexed queue picture ID
            qpid = decode(h, day_code(PQ_DAY_CODE))
            qp = QueuePicture.objects.get(pk=qpid)
            c = qp.course
        else:
            # the hex code should be a course ID, and key rotation should proceed as usual
            context = generic_context(request, h)
            cid = decode(h, context['user_session'].decoder)
            c = Course.objects.get(pk=cid)
        # always ensure that the user is instructor in the course
        if not (c.manager == context['user'] or c.instructors.filter(id=context['user'].id)):
            log_message('ACCESS DENIED: Invalid course parameter', context['user'])
            return render(request, 'presto/forbidden.html', context)
    except Exception, e:
        report_error(context, e)
        return render(request, 'presto/error.html', context)
예제 #7
0
def estafette_view(request, **kwargs):
    h = kwargs.get('hex', '')
    context = generic_context(request, h)
    # check whether user can have instructor role
    if not change_role(context, 'Instructor'):
        return render(request, 'presto/forbidden.html', context)

    # check whether estafette case must be deleted
    if kwargs.get('action', '') == 'delete-case':
        try:
            ecid = decode(h, context['user_session'].decoder)
            ec = EstafetteCase.objects.get(pk=ecid)
            # remember the estafette that is being edited
            e = ec.estafette
            ec.delete()
        except Exception, e:
            report_error(context, e)
            return render(request, 'presto/error.html', context)
예제 #8
0
def test_file_type(request, **kwargs):
    context = generic_context(request)
    context['page_title'] = 'Presto'
    if not request.FILES:
        return render(request, 'presto/test_file_type.html', context)
    # check whether uploaded file is valid
    f = request.FILES['test']
    upload_size = f.size / 1048576.0  # size in MB
    ext = os.path.splitext(f.name)[1]
    inform_user(context, 'File uploaded',
                'Size: %3.2f MB<br/>Extension: %s' % (upload_size, ext))
    file_too_big = upload_size > MAX_UPLOAD_SIZE
    if file_too_big:
        warn_user(context, 'File too big',
                  'Size exceeds %3.2f MB' % MAX_UPLOAD_SIZE)
    else:
        try:
            # save upload as temporary file
            handle, fn = mkstemp()
            os.close(handle)
            with open(fn, 'wb+') as dst:
                for chunk in f.chunks():
                    dst.write(chunk)
            # NOTE: openpyxl requires files to have a valid extension!
            # NOTE: extension is always added for uniformity & tracing purposes
            os.rename(fn, fn + ext)
            fn += ext
            # try to open the file with the correct application
            if ext in ['.docx']:
                doc = Document(fn)
            elif ext in ['.pptx']:
                prs = Presentation(fn)
            elif ext in ['.xlsx']:
                wbk = load_workbook(fn)
            elif ext == '.pdf':
                pdf = PdfFileReader(fn)
        # NOTE: assumes that incorrect file types will raise an error
        except Exception, e:
            warn_user(context, 'Invalid_file',
                      'ERROR while checking file %s: %s' % (fn, str(e)))
예제 #9
0
파일: views.py 프로젝트: pwgbots/presto
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)
예제 #10
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)
예제 #11
0
def course(request, **kwargs):
    h = kwargs.get('hex', '')
    act = kwargs.get('action', '')
    context = generic_context(request, h)
    # check whether user can view this course
    try:
        cid = decode(h, context['user_session'].decoder)
        if act == 'delete-relay':
            # in this case, the course relay ID is passed as hex
            ce = CourseEstafette.objects.get(pk=cid)
            c = ce.course
        else:
            # otherwise the course ID
            c = Course.objects.get(pk=cid)
        # ensure that user is instructor in the course
        if not (c.manager == context['user']
                or c.instructors.filter(id=context['user'].id)):
            log_message('ACCESS DENIED: Invalid course parameter',
                        context['user'])
            return render(request, 'presto/forbidden.html', context)
    except Exception, e:
        report_error(context, e)
        return render(request, 'presto/error.html', context)
예제 #12
0
파일: views.py 프로젝트: pwgbots/presto
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)
예제 #13
0
def demo(request):
    context = generic_context(request)
    context['page_title'] = 'Presto demonstration'
    return render(request, 'presto/demo.html', context)
예제 #14
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)
예제 #15
0
def history_view(request, **kwargs):
    h = kwargs.get('hex', '')
    context = generic_context(request, h)
    # check whether user can have student role
    if not change_role(context, 'Student'):
        return render(request, 'presto/forbidden.html', context)
        
    # check whether user is enrolled in any courses
    cl = Course.objects.filter(coursestudent__user=context['user']
        ).annotate(count=Count('coursestudent'))
    # add this list in readable form to the context (showing multiple enrollments)
    context['course_list'] = ',&nbsp;&nbsp; '.join(
        [c.title() + (' <span style="font-weight: 800; color: red">%d&times;</span>' %
            c.count if c.count > 1 else '') for c in cl
        ])

    # student (but also instructor in that role) may be enrolled in several courses
    # NOTE: "dummy" students are included, but not the "instructor" students
    csl = CourseStudent.objects.filter(user=context['user'], dummy_index__gt=-1)

    # get the estafettes for all the student's courses (even if they are not active)
    cel = CourseEstafette.objects.filter(
        is_deleted=False, is_hidden=False, course__in=[cs.course.id for cs in csl])
    # add this list in readable form to the context
    context['estafette_list'] = ',&nbsp;&nbsp; '.join([ce.title() for ce in cel])

    # get the set of all the course student's current participations
    pl = Participant.objects.filter(estafette__is_deleted=False,
        estafette__is_hidden=False, student__in=[cs.id for cs in csl]
        ).order_by('-estafette__start_time')
    
    # if user is a "focused" dummy user, retain only this course user's participations
    if context.has_key('alias'):
        pl = pl.filter(student=context['csid'])

    # if h is not set, show the list of participations as a menu
    if h == '':
        # start with an empty list (0 participations)
        context['participations'] = []
        # for each participation, create a context entry with properties to be displayed
        for p in pl:
            lang = p.estafette.course.language  # estafettes "speak" the language of their course
            steps = p.estafette.estafette.template.nr_of_legs()
            part = {'object': p,
                    'lang': lang,
                    'start': lang.ftime(p.estafette.start_time),
                    'end': lang.ftime(p.estafette.end_time),
                    'next_deadline': p.estafette.next_deadline(),
                    'steps': steps,
                    'hex': encode(p.id, context['user_session'].encoder),
                    'progress': '%d/%d' % (p.submitted_steps(), steps),
                    }
            context['participations'].append(part)
        # and show the list as a menu
        context['page_title'] = 'Presto History' 
        return render(request, 'presto/history_view.html', context)

    # if we get here, h is set, which means that a specific estafette has been selected
    try:
        # first validate the hex code
        pid = decode(h, context['user_session'].decoder)
        p = Participant.objects.get(pk=pid)
        context['object'] = p
        # encode again, because used to get progress chart
        context['hex'] = encode(p.id, context['user_session'].encoder)
        # add progress bar data
        context['things_to_do'] = p.things_to_do()
        # do not add participant name popups
        context['identify'] = False
        # add context fields to be displayed when rendering the template
        set_history_properties(context, p)
        # show the full estafette history using the standard page template
        context['page_title'] = 'Presto History' 
        return render(request, 'presto/estafette_history.html', context)        
            
    except Exception, e:
        report_error(context, e)
        return render(request, 'presto/error.html', context)
예제 #16
0
파일: progress.py 프로젝트: pwgbots/presto
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")
예제 #17
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)
예제 #18
0
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)
예제 #19
0
def awards(request):
    context = generic_context(request)

    # check awardable achievements, and create/update badges if appropriate

    # (1) get participant badges issued so far
    b_list = PrestoBadge.objects.filter(participant__isnull=False).filter(
        participant__student__user=context['user']).distinct()
    badge_dict = {}
    for b in b_list:
        # get the badge relevant properties as a dict
        d = b.as_dict()
        # and add to this dict the badge object and its ID hex code
        d['object'] = b
        d['hex'] = encode(b.id, context['user_session'].encoder)
        badge_dict[b.id] = d

    # (2) get user's finished steps in relays that issue badges
    # NOTE: no badges for instructor participants (dummy index must be 0)
    p_list = Participant.objects.filter(student__user=context['user'],
                                        student__dummy_index=0,
                                        estafette__with_badges=True)
    # ignore "clone" assignments, as only their original represents the actual work
    # NOTE: this also prevents rewards for a "selfie" review
    a_list = Assignment.objects.filter(participant__in=p_list,
                                       time_uploaded__gt=DEFAULT_DATE,
                                       clone_of__isnull=True)
    r_list = PeerReview.objects.filter(assignment__in=a_list,
                                       time_submitted__gt=DEFAULT_DATE,
                                       grade__gt=2).distinct()

    # (3) create badges for those steps not already rewarded
    for r in r_list:
        # NOTE: only consider reviews of assignments that have an uploaded successor,
        #       or are final reviews, or are instructor reviews
        if (r.reviewer.student.dummy_index < 0 or r.final_review_index > 0
                or (r.assignment.successor != None
                    and r.assignment.successor.time_uploaded != DEFAULT_DATE)):
            # badge must match participant and level
            if not lookup_badge(badge_dict, r.assignment.leg.number,
                                r.assignment.participant):
                b = PrestoBadge.objects.create(
                    course=r.assignment.participant.student.course,
                    participant=r.assignment.participant,
                    referee=None,
                    levels=r.assignment.leg.template.nr_of_legs(),
                    attained_level=r.assignment.leg.number)
                d = b.as_dict()
                d['object'] = b
                d['hex'] = encode(b.id, context['user_session'].encoder)
                badge_dict[b.id] = d

    # (4) get referee decisions that revised the user's grade to 3+ stars
    r_list = PeerReview.objects.filter(
        assignment__in=a_list,
        is_appeal=True,
        time_appeal_assigned__gt=DEFAULT_DATE).distinct()
    a_list = Appeal.objects.filter(review__in=r_list,
                                   time_decided__gt=DEFAULT_DATE,
                                   grade__gt=2).distinct()

    # (5) create badges for those steps not already rewarded
    for a in a_list:
        ara = a.review.assignment
        # badge must match participant and level
        if not lookup_badge(badge_dict, ara.leg.number, ara.participant):
            b = PrestoBadge.objects.create(
                course=ara.participant.student.course,
                participant=ara.participant,
                referee=None,
                levels=ara.leg.template.nr_of_legs(),
                attained_level=ara.leg.number)
            d = b.as_dict()
            d['object'] = b
            d['hex'] = encode(b.id, context['user_session'].encoder)
            badge_dict[b.id] = d

    # (6) add list of entries to context, sorted by template name and then levels attained
    context['participant_badges'] = badge_dict.values()
    context['participant_badges'].sort(cmp=compare_badge_info)

    # (7) get referee badges issued so far
    # NOTE: referee badges are awarded only upon passing a referee exam, hence not created here
    ref_badge_dict = {}
    for b in PrestoBadge.objects.filter(referee__isnull=False).filter(
            referee__user=context['user']):
        d = b.as_dict()
        d['object'] = b
        d['hex'] = encode(b.id, context['user_session'].encoder)
        ref_badge_dict[b.referee.id] = d

    # add list of entries to context, sorted by template name and then levels attained
    context['referee_badges'] = ref_badge_dict.values()
    context['referee_badges'].sort(cmp=compare_badge_info)

    # get referee LoA's issued so far (with ID encoded as hex)
    l_dict = {}
    # first add participant LoA's
    # NOTE: keys prefixed by P to distinguish participant IDs from referee IDs
    for l in LetterOfAcknowledgement.objects.filter(
            participant__student__user=context['user']):
        l_dict['P%d' % l.participant.id] = {
            'object': l,
            'hex': encode(l.id, context['user_session'].encoder)
        }
    # NOTE: referee LoA's have their estafette ID as key
    for l in LetterOfAcknowledgement.objects.filter(
            referee__user=context['user']):
        l_dict[l.estafette.id] = {
            'object': l,
            'hex': encode(l.id, context['user_session'].encoder)
        }

    # get the user's referee object instances (created upon passing a referee exam)
    r_list = Referee.objects.filter(user=context['user'])

    # get decided appeal cases, and tally them (1) per course estafette (2) per leg
    re_dict = {}
    a_list = Appeal.objects.filter(referee__in=r_list,
                                   time_decided__gt=DEFAULT_DATE)

    # NOTE: to qualify for a referee letter of acknowledgement, referees must have
    #       made at least 1 appeal decision (so re_dict remains empty if a_list is empty)
    for a in a_list:
        e = a.review.assignment.participant.estafette
        l = a.review.assignment.leg.number
        # update step count if estafette already has an entry ...
        if re_dict.has_key(e.id):
            if re_dict[e.id].has_key(l):
                re_dict[e.id][l] += 1
            else:
                re_dict[e.id][l] = 1
            re_dict[e.id]['first'] = min(re_dict[e.id]['first'],
                                         a.time_decided)
            re_dict[e.id]['last'] = max(re_dict[e.id]['last'], a.time_decided)
        # ... or create a new entry
        else:
            re_dict[e.id] = {
                'ce': e,
                'ref': a.referee,
                'first': a.time_decided,
                'last': a.time_decided,
                'sum': 0,
                'cnt': 0,
                l: 1
            }
        # collect statistics on appraisal of the decisions (by default, assume neutral: 2)
        if a.predecessor_appraisal > 0:
            re_dict[e.id]['sum'] += a.predecessor_appraisal
        else:
            re_dict[e.id]['sum'] += 2
        if a.successor_appraisal > 0:
            re_dict[e.id]['sum'] += a.successor_appraisal
        else:
            re_dict[e.id]['sum'] += 2
        re_dict[e.id]['cnt'] += 2

    # re_dict now contains all "entitlements" for referee LoA's
    for e in re_dict.keys():
        # count refereed steps while making a list of refereed step numbers
        ns = re_dict[e]['ce'].estafette.template.nr_of_legs()
        acc = 0
        rs = []
        for i in range(1, ns + 1):
            if re_dict[e].has_key(i):
                rs.append(i)
                acc += re_dict[e][i]
        # format this as a nice phrase, e.g. "steps 1, 2 and 4 (of 4)"
        if ns == 1:
            steps = 'the single step'
        elif len(rs) == 1:
            steps = 'step %d (of %d)' % (rs[0], ns)
        elif len(rs) == ns:
            steps = 'all %d steps' % ns
        else:
            steps = 'steps %s and %d (of %d)' % (', '.join(
                [str(s) for s in rs[:-1]]), rs[-1], ns)
        # compute the average appreciation on a scale from -1 to 1
        appr = 0
        print 'count=%d' % re_dict[e]['cnt']
        print 'sum=%d' % re_dict[e]['sum']
        if re_dict[e]['cnt'] > 0:
            appr = re_dict[e]['sum'] / float(re_dict[e][
                'cnt']) - 2  # subtract 2 because frown = 1 and smile = 3
        if l_dict.has_key(e):
            # upgrade letter if needed
            changed = False
            l = l_dict[e]['object']
            if l.appeal_case_count != acc:
                l.appeal_case_count = acc
                l.extra_hours = acc * re_dict[e][
                    'ce'].estafette.hours_per_appeal
                l.time_first_case = re_dict[e]['first']
                l.time_last_case = re_dict[e]['last']
                changed = True
            if l.average_appreciation != appr:
                l.average_appreciation = appr
                changed = True
            if changed:
                l.save()
        else:
            # create new letter if user has indeed decided on one or more appeal cases
            l = LetterOfAcknowledgement.objects.create(
                estafette=re_dict[e]['ce'],
                referee=re_dict[e]['ref'],
                step_list=steps,
                time_first_case=re_dict[e]['first'],
                time_last_case=re_dict[e]['last'],
                appeal_case_count=acc,
                extra_hours=acc * re_dict[e]['ce'].estafette.hours_per_appeal,
                average_appreciation=appr)
            l_dict[e] = {
                'object': l,
                'hex': encode(l.id, context['user_session'].encoder)
            }

    context['letters'] = l_dict

    context['page_title'] = 'Presto Awards'
    return render(request, 'presto/awards.html', context)
예제 #20
0
파일: views.py 프로젝트: pwgbots/presto
def setting_view(request):
    context = generic_context(request)
    context['page_title'] = 'Presto Settings' 
    return render(request, 'presto/setting_view.html', context)
예제 #21
0
def demo_login(request):
    context = generic_context(request)
    context['page_title'] = 'Presto demonstration'
    # define two *deterministic* 32 hex digit keys to encode/decode the ID parameters
    de_key = md5(context['page_title']).hexdigest()
    ds_key = md5(de_key).hexdigest()
    # if PIN is passed, hex is the encoded ID
    pin = request.POST.get('pin', '').strip().upper()
    h = request.POST.get('hex', '').strip()
    if pin and h:
        de_hex = h[:32]
        ds_hex = h[-32:]
        # see if hex encodes a course student ID
        try:
            ds = CourseStudent.objects.get(id=decode(ds_hex, ds_key))
        except:
            return render(request, 'presto/forbidden.html', context)
        # if student PIN matches the one entered in the PIN dialog, focus on this participant
        # and show the student view
        if pin == ds.pin():
            # log in as the (one-and-only!) demo user
            usr = User.objects.get(username=settings.DEMO_USER_NAME)
            usr.backend = settings.DEMO_USR_BACKEND
            login(request, usr)
            context = generic_context(request)
            # pass "clear_sessions" = True so that old "focused" user session record is deleted
            set_focus_and_alias(context, ds.id, ds.particulars, True)
            return student(request)
        # if not, notify the user and show the initial demo dialog
        warn_user(
            context, 'Invalid PIN',
            'Please try again, or cancel to log in with a different alias.')
        try:
            de = CourseEstafette.objects.get(id=decode(de_hex, de_key))
        except:
            return render(request, 'presto/forbidden.html', context)
        context['estafette'] = de.title
        context['de_hex'] = de_hex
        context['ds_hex'] = encode(ds.id, ds_key)
        return render(request, 'presto/demo.html', context)

    # no PIN? then expect an alias and a code
    b36 = request.POST.get('code', '').strip()
    alias = request.POST.get('alias', '').strip()
    # warn user if alias is invalid
    if not validate_alias(context, alias):
        return render(request, 'presto/demo.html', context)

    # validate this code as a "running" DEMO course estafette id
    demo_courses = Course.objects.filter(code='DEMO')
    if demo_courses:
        # only get the active estafettes for the DEMO course
        demo_estafettes = CourseEstafette.objects.filter(
            course__id__in=demo_courses).exclude(
                start_time__gte=timezone.now()).exclude(
                    end_time__lte=timezone.now())
        if demo_estafettes:
            for de in demo_estafettes:
                if b36 == de.demonstration_code():
                    # get the demonstration user
                    usr = User.objects.get(username=settings.DEMO_USER_NAME)
                    # get the highest dummy index for this course
                    dsl = CourseStudent.objects.filter(user=usr,
                                                       course=de.course,
                                                       dummy_index__gt=0)
                    if dsl:
                        hdi = max([ds.dummy_index for ds in dsl])
                    else:
                        hdi = 0
                    # see if alias matches any "dummy" course student
                    ds = dsl.filter(particulars=alias).first()
                    if ds:
                        # existing user? then ask for PIN
                        context['estafette'] = de.title
                        context['de_hex'] = encode(de.id, de_key)
                        context['ds_hex'] = encode(ds.id, ds_key)
                    else:
                        # create new "dummy" course student
                        ds = CourseStudent(course=de.course,
                                           user=usr,
                                           dummy_index=hdi + 1,
                                           particulars=alias,
                                           time_enrolled=timezone.now(),
                                           last_action=timezone.now())
                        ds.save()
                        # log in as the (one-and-only!) demo user
                        usr.backend = settings.DEMO_USR_BACKEND
                        login(request, usr)
                        context = generic_context(request)
                        # create a *deterministic* PIN based on the user's alias and dummy index
                        context['pin'] = ds.pin()
                        context['alias'] = ds.particulars
                        # focus on this "dummy" student
                        set_focus_and_alias(context, ds.id, ds.particulars)
                    # show the demo dialog
                    return render(request, 'presto/demo.html', context)
            # FALL-THROUGH: matching estafette
            warn_user(
                context, 'Invalid code',
                'The code you entered does not identify a demonstration relay.'
            )
        else:
            warn_user(
                context, 'No demonstration relay',
                'Presently, no demonstration relays are open for enrollment.')
    else:
        warn_user(context, 'No demonstration course',
                  '<em>Note:</em> Demonstration courses must have code DEMO.')
    if alias == '':
        warn_user(
            context, 'Alias required',
            'Please provide a name that will identify you as a demonstration user.<br>'
            +
            '<em>This name must start with a letter and be at least 6 characters long.</em>'
        )
    return render(request, 'presto/demo.html', context)
예제 #22
0
파일: views.py 프로젝트: pwgbots/presto
def announcements(request):
    context = generic_context(request)
    context['page_title'] = 'Presto Announcements' 
    return render(request, 'presto/announcements.html', context)
예제 #23
0
def verify(request, **kwargs):
    context = generic_context(request)
    # an authentication code in the URL should generate a JavaScript AJAX call
    context['hex'] = kwargs.get('hex', '')
    context['page_title'] = 'Presto Award Verification'
    return render(request, 'presto/verify.html', context)