Esempio n. 1
0
 def testGetPolicy(self):
     a = Assignment('A01')
     a.due = '20140615-0000'
     early1 = LatePolicy('-3d:+10', a.due)
     early2 = LatePolicy('-1d:+5', a.due)
     late1 = LatePolicy('+1d:-15', a.due)
     late2 = LatePolicy('+2d:-30', a.due)
     tests = {
         ('20140614-0000', None): None,  #no polices
         ('20140616-0000', None): None,
         ('20140614-1200', (late1, late2)): None,  #early
         ('20140614-1200', (late2,)): None,
         ('20140614-1200', (early1,)): early1,
         ('20140614-1200', (early1, early2, late1)): early2,
         ('20140610-1200', (early1, early2, late1)): early1,
         ('20140615-1200', (early1, early2)): None, #late
         ('20140615-1200', (early1,)): None,
         ('20140615-1200', (early1, late1)): late1,
         ('20140616-1200', (early1, late1)): late1,
         ('20140616-1200', (early1, late1, late2)): late2,
         ('20140619-1200', (early1, late1, late2)): late2,
     }
     for (timestamp, policies), expected in tests.items():
         a.policies = list(policies) if policies else None
         self.assertEqual(a.getPolicy(timestamp), expected)
Esempio n. 2
0
 def testLateOffset(self):
     """ Makes sure offsets from A01's deadline are correct. """
     # vs: A01-20380119-0314
     tests = {
         '20380119-0314': '-0d 0h 00m',
         '20380119-0315': '+0d 0h 01m',
         '20380118-0211': '-1d 1h 03m',
         '20380119-0414': '+0d 1h 00m',            
     }
     for stamp, offset in tests.items():
         a = Assignment('A01')
         self.assertEqual(a.getLateOffset(stamp), offset)
Esempio n. 3
0
def displayAssignmentSubmissions(user, assignmentName, 
                                 brief=False, master=False):
    """
    Displays all the submissions the user made for this assignment.
    
    In brief mode, this will just be a list of files, including the grade
    for each one.  Each listing will be a button (or link, if in master mode)
    to the appropriate submission view.
    
    If in full mode (that is, brief=False), will show each submission expanded 
    within this view.
    
    At the top off all submissions, will include a header listing the 
    assignment and the final grade based on last submission and late policy
    adjustments.
    """
    assignment = Assignment(assignmentName)
    files = tamarin.getSubmissions(user=user, assignment=assignmentName)
    
    # calculate final grade and status for this assignment
    # (Assuming that even ungraded and grader-error submissions count as
    # submissions, we only need the grade of the last submission to have an 
    # accurate final grade.)
    grade = '<i>Not yet submitted.</i>'
    reason = None

    if files:
        lastSubmit = files[-1]
        if tamarin.SUBMITTED_ROOT in lastSubmit:
            grade = '<i>Not yet graded.</i>'
            lastFile = SubmittedFile(os.path.basename(lastSubmit))
        else:
            lastFile = GradedFile(os.path.basename(lastSubmit))
            grade = lastFile.getAdjustedGrade(len(files))
            lateness = lastFile.getLateGradeAdjustment()
            resubmits = lastFile.getResubmissionGradeAdjustment(len(files))
            
            if lateness or resubmits:
                # explain grade calcs
                reason = '[= ' + str(lastFile.grade)
                if lateness:
                    reason += ' ' + str(lateness)
                    offset = lastFile.getLateOffset()
                    label = 'early' if offset[0] == '-' else 'late'
                    reason += ' <i>(' + label + ' ' + offset + ')</i>'
                if resubmits:
                    reason += ' ' + str(resubmits)
                    reason += ' <i>(' + str(len(files) - 1) + \
                              '&nbsp;resubmits)</i>'
                reason += ']'
            elif lastFile.isLate():
                # just a little informational timestamping
                reason = '<small>' + lastFile.getLateOffset() + '</small>'
            
            # mark grade if not verified yet
            if not lastFile.humanVerified:
                grade = '<span class="unverified">' + str(grade) + \
                        tamarin.UNVERIFIED_GRADE_LABEL + '</span>'
    else:
        #no files submitted at all yet
        if assignment.isTooLate():
            #can't submit, so grade goes to 0
            grade = 0
            reason = '<i>Too late to submit.</i>'

    # print submission list header, starting with assignment grade
    print('<div class="submissionList">')
    print('<div class="assignment">')
    print('<table class="assignment"><tr><td class="assignment">')
    print('<b>' + assignment.name + '</b> &nbsp;')
    print('<small>(Due: ' + assignment.due + '. ')
    print('Total: ' + str(assignment.maxScore) + ' points.)</small></td>')
    print('<td class="grade"><b>Grade:</b> ' + str(grade) + '</td>')
    if reason:
        print('<td class="reason">' + reason + '</td>')
    print('</tr></table>')

    #list contents
    if files and brief:
        print('<ul>')
    for f in files:
        if brief:
            if master:
                print('<li><a href="masterview.py?submission=' + 
                      os.path.basename(f) + '"', end='')
                if tamarin.MASTER_LINKS_OPEN_NEW_WINDOW:
                    print(' target="_blank"', end='')
                print('>' + os.path.basename(f) + '</a>', end=' ') 
            else:
                print('<li><input type="submit" name="submission" value="' + 
                      os.path.basename(f) + '">', end=' ')
            if tamarin.SUBMITTED_ROOT in f:
                print('&nbsp; [<i>Not yet graded.</i>]')
            else:
                graded = GradedFile(os.path.basename(f))
                shortGrade = str(graded.grade)
                if not graded.humanVerified:
                    shortGrade += tamarin.SHORT_UNVERIFIED_GRADE_LABEL
                if graded.humanComment:
                    shortGrade += tamarin.HUMAN_COMMENT_LABEL
                print('&nbsp; [' + shortGrade + ']')
        else:
            displaySubmission(os.path.basename(f), master)
          
    #list footer
    if files and brief:
        print('</ul>')
    print('</div></div>')
Esempio n. 4
0
def validateUploadedFile(form):
    """
    Processes a file upload.  
    
    Assumes the passed form is a valid query submission.  On an error, 
    reports what went wrong to the user. Otherwise confirms that the 
    user wants to submit the uploaded file for grading.
    """
    printHeader("File Upload Results")
    try: 
        print('<h2>File Upload Results</h2>')
        print('<p>')

        # get filename and check that file was properly uploaded
        assert 'file' in form and 'pass' in form, "Invalid form submission"
        filename = form['file'].filename
        if not filename:
            raise TamarinError('NO_FILE_UPLOADED')
        else:
            print('<!--Original filename: ' + filename + '-->')
            filename = stripFilename(filename)  #defined below
            print('<b>Filename:</b> ' + filename + '<br>')
            filecontents = form.getfirst('file')
            if not filecontents:
                raise TamarinError('EMPTY_FILE')
            
        # validate filename
        checkFilename(filename)
        
        # authenticate user 
        match = re.match(tamarin.UPLOADED_RE, filename)
        assert match  #because filename is valid from above
        username = match.group(1)
        username = username.lower()  #for any initial uppercase letter
        assignmentName = match.group(2)
        extension = match.group(3)
        password = form.getfirst('pass')
        
        print('<b>Username:</b> ' + username + '<br>')
        print('<b>Password:</b> ', end='')
        if password: 
            print('*' * len(password) + '<br>')
        else:
            print('[missing]<br>')      
        
        tamarin.authenticate(username, password)
    
        # validate that assignment exists
        print('<b>Assignment:</b> ' + assignmentName)  #... no <br> yet
        assignment = Assignment(assignmentName) #may throw TamarinError
        
        # confirm any type-specific requirements...
        # right extension?
        if extension != assignment.type.fileExt:
            raise TamarinError('WRONG_EXTENSION',
                'Your file is .' + extension + 
                ' but this assignment requires .' + assignment.type.fileExt)
        
        # initial cap?
        if assignment.type.initialCap and not re.match(r"^[A-Z]", filename):
            raise TamarinError('NO_INITIAL_CAP', filename)

        # check that file contents are plain text (if necessary)
        if assignment.type.encoding:
            try:
                # convert bytes to str
                filecontents = filecontents.decode(
                                    encoding=assignment.type.encoding)
                # files often uploaded in binary form from various OSs, 
                # so adjust line-endings
                # XXX: Still need this in Python3?
                filecontents = filecontents.replace('\r\n', '\r')
                # \n comes out properly as 0D0A on Windows
                filecontents = filecontents.replace('\r', '\n')  

            except UnicodeError as err:
                # this problem is often due to a copy-and-paste thing from a 
                # different encoding where a couple odd characters (like quotes
                # and such) come from a word processor.  So this will help 
                # students track down where their bad chars are in this most
                # common non-ASCII case
                lineCount = 1
                charCount = 1
                for char in filecontents: 
                    if char > 128:
                        print("<p><b>First non-ASCII character:</b>: ")
                        print("line " + str(lineCount) + ", char " + 
                              str(charCount) + "<br>")
                        break
                    elif char == '\n':  
                        # NOTE: line count only correct for Unix and Windows; 
                        # not old Mac, etc
                        lineCount += 1
                        charCount = 1
                    else:
                        charCount += 1
                raise TamarinError('BINARY_FILE', "This assignment requires "
                    "plain text files using the " + assignment.type.encoding +
                    " (or compatible) encoding, but: " + str(err))

        # Ready to accept based on submission itself.

        # Now see if late or already submitted (for warnings) or verified        
        # Reuse filename (without extension) from above
        wildFilename = filename.replace('.', '*.', 1)        
        alreadySubmitted = glob.glob(os.path.join(tamarin.SUBMITTED_ROOT, 
                                                  wildFilename))
        alreadyGraded = glob.glob(os.path.join(assignment.path, wildFilename))
        # but not all files in graded == a graded submission file if submitted
        # ext is same as grader output file ext, so remove grader output files
        for ag in alreadyGraded[:]: 
            if re.match(tamarin.GRADED_RE, os.path.basename(ag)):              
                alreadyGraded.remove(ag)   
        
        # determine lateness, so can print it here
        if assignment.isLate():
            print(' (<i>Late: </i>' + assignment.getLateOffset() + ')') 
            if assignment.isTooLate():
                raise TamarinError('SUBMISSION_TOO_LATE')
            if not tamarin.MAY_RESUBMIT_LATE and \
                    (alreadySubmitted or alreadyGraded):
                raise 'RESUBMISSION_LATE'
      
        # has a human already verified a previous submission?
        if not tamarin.MAY_RESUMBIT_AFTER_HUMAN and alreadyGraded:
            # check if a graded file has also been verified
            for f in alreadyGraded: 
                gradedF = GradedFile(os.path.basename(f))
                if gradedF.humanVerified:
                    # oops for student: already verified
                    raise TamarinError('PREVIOUS_SUBMISSION_VERIFIED')
                
        # display file contents (if appropriate)
        print('<p><b>Uploaded Code:</b></p>')
        print('<pre ' if assignment.type.preformatted else '<div ')
        print('class="code">')
        if not assignment.type.encoding:
            print('[ <i>binary file format: '
                  'cannot display contents here</i> ]')
        else:
            # prevent disappearing code due to code test looking like html tags
            if assignment.type.preformatted:
                print(html.escape(filecontents))
            else:
                print(html.escape(filecontents).replace('\n', '<br>\n'))            
        print('</pre>' if assignment.type.preformatted else '</div>')
      
        # Done!
        # write file (could result in an io error, handed as UNHANDLED below)
        uploadedFilename = os.path.join(tamarin.UPLOADED_ROOT, filename)
        overwrite = glob.glob(uploadedFilename)  #see if file already exists
        outfile = open(uploadedFilename, 
                       'w' if assignment.type.encoding else 'wb')
        outfile.write(filecontents)
        outfile.close()

        # give success message
        print('<p>')
        print('Your code (as shown above) has been uploaded to Tamarin. ')
        if overwrite:
            print('(Doing so overwrote a previously uploaded '
                  'but unsubmitted file of the same name.) ')
        print('<i>It has not yet been submitted.</i> ')

        # warn if already submitted (and/or graded) this assignment
        if alreadySubmitted or alreadyGraded:
            print('<p><b><i>Warning:</i></b> You already have ')
            if alreadySubmitted:
                print(str(len(alreadySubmitted)) + ' file(s) submitted '
                      '(but not yet graded) ')
                if alreadyGraded:
                    print('and ')
            if alreadyGraded:
                print(str(len(alreadyGraded)) + ' file(s) graded ')
            print('for this ' + str(assignment) + ' assignment. ')
            submitCount = len(alreadySubmitted) + len(alreadyGraded)
            if tamarin.RESUBMISSION_PENALTY:
                print('If you submit this uploaded file, it will be a ' +
                      '(resubmission * ' + str(submitCount) + '), ')
                print('and so its final score will be reduced by ' + 
                      str(round(submitCount * tamarin.RESUBMISSION_PENALTY, 
                                tamarin.GRADE_PRECISION)) + 
                      '.</p>')
                
        # provide submit button
        print("<p>If you are ready to officially submit this uploaded file "
              "for grading, click the button below. </p>")
        print()
        print('<form action="' + tamarin.CGI_URL + 'submit.py" '
              'method="post" class="html">')
        print('<input type="hidden" name="uploaded" value="' + filename + '">')
        print('<input type="submit" value="Submit this file" '
              'class="centered">')
        print('</form>')  
    
    except TamarinError as err:
        printError(err)
        print('<p>')
        print('Due to the above error, your uploaded file was not saved. ' 
            'Please <a href="' + tamarin.CGI_URL + 'upload.py' + 
            '">return to the upload page</a> and try again.')
        print('</p>')   
    except:
        printError('UNHANDLED_ERROR')
    finally: 
        printFooter()