Exemplo n.º 1
0
 def extract_archive(self, filename):
     path = os.path.dirname(filename)
     if filename.endswith('.zip'):
         logger.debug("Unzip archive %s", filename)
         import zipfile
         with zipfile.ZipFile(filename) as zf:
             zf.extractall(path)
Exemplo n.º 2
0
 def require_success_message(self, response):
     document = html5lib.parse(response.content,
                               transport_encoding=response.encoding)
     msg = document.find('.//h:span[@id="goodMsg1"]', NS)
     if msg is None:
         raise ParserError("No goodMsg1 in POST response", response,
                           'Post data:\n%s' % pprint.pformat(self._data),
                           'Files:\n%s' % pprint.pformat(self.files))
     logger.debug("goodMsg1: %s", element_text_content(msg))
Exemplo n.º 3
0
def is_course_id_valid(session, course_id=None):
    if course_id is None:
        course_id = session.course_id
    url = (
        'https://bb.au.dk/webapps/blackboard/execute/' +
        'courseMain?course_id=%s' % course_id)
    response = session.get(url)
    document = html5lib.parse(response.content, encoding=response.encoding)

    content_panel_path = './/h:div[@id="contentPanel"]'
    content_panel = document.find(content_panel_path, NS)
    if content_panel is None:
        logger.debug("is_course_id_valid: No contentPanel")
        return True
    classes = (content_panel.get('class') or '').split()
    return 'error' not in classes
Exemplo n.º 4
0
def is_course_id_valid(session, course_id=None):
    if course_id is None:
        course_id = session.course_id
    url = ('https://%s/webapps/blackboard/execute/' % DOMAIN +
           'courseMain?course_id=%s' % course_id)
    response = session.get(url)
    document = html5lib.parse(response.content,
                              transport_encoding=response.encoding)

    content_panel_path = './/h:div[@id="contentPanel"]'
    content_panel = document.find(content_panel_path, NS)
    if content_panel is None:
        logger.debug("is_course_id_valid: No contentPanel")
        return True
    classes = (content_panel.get('class') or '').split()
    return 'error' not in classes
Exemplo n.º 5
0
    def get_attempt_files(self, attempt):
        assert isinstance(attempt, Attempt)
        keys = 'submission comments files'.split()
        st = self.get_attempt_state(attempt)
        if all(k in st for k in keys) and 'score' not in st:
            logger.debug("Refresh attempt %s since it was fetched in an old " +
                         "version of bbfetch", attempt.id)
        elif all(k in st for k in keys) and st['score'] != attempt.score:
            logger.debug("Refresh attempt %s since its score has changed",
                         attempt.id)
        if (not all(k in st for k in keys) or
                'score' not in st or
                st['score'] != attempt.score):
            self.refresh_attempt_files(attempt)
            st = self.get_attempt_state(attempt)
        used_filenames = set(['comments.txt'])
        files = []

        def add_file(name, **data):
            if name in used_filenames:
                base, ext = os.path.splitext(name)
                name = base + attempt.id + ext
            data['filename'] = name
            used_filenames.add(name)
            files.append(data)

        if st['submission']:
            add_file('submission.txt', contents=st['submission'])
        if st['comments']:
            add_file('student_comments.txt', contents=st['comments'])
        if st.get('feedback'):
            used_filenames.remove('comments.txt')
            add_file('comments.txt', contents=st['feedback'])
        rubrics = self.get_rubrics(attempt)
        if rubrics:
            add_file('rubric.txt',
                     contents='\n'.join(r.get_form_as_text() for r in rubrics))
        for o in st.get('feedbackfiles', []):
            add_file(o['filename'], **o)
        for o in st['files']:
            add_file(o['filename'], **o)
        return files
Exemplo n.º 6
0
 def extract_zip(self, filename):
     path = os.path.dirname(filename)
     logger.debug("Unzip archive %s", filename)
     import zipfile
     with zipfile.ZipFile(filename) as zf:
         zf.extractall(path)
Exemplo n.º 7
0
 def extract_tar(self, filename):
     path = os.path.dirname(filename)
     logger.debug("Extract tar archive %s", filename)
     import tarfile
     with tarfile.open(filename) as f:
         f.extractall(path)
Exemplo n.º 8
0
 def extract_rar(self, filename):
     path = os.path.dirname(filename)
     logger.debug("Extract rar archive %s", filename)
     import rarfile
     with rarfile.RarFile(filename) as f:
         f.extractall(path)
Exemplo n.º 9
0
def submit_grade(session, attempt_id, is_group_assignment,
                 grade, text, filenames, rubrics):
    assert isinstance(session, BlackboardSession)
    if is_group_assignment:
        url = ('https://bb.au.dk/webapps/assignment/' +
               'gradeAssignmentRedirector' +
               '?course_id=%s' % session.course_id +
               '&groupAttemptId=%s' % attempt_id)
    else:
        url = ('https://bb.au.dk/webapps/assignment/' +
               'gradeAssignmentRedirector' +
               '?course_id=%s' % session.course_id +
               '&attempt_id=%s' % attempt_id)
    # We need to fetch the page to get the nonce
    response = session.get(url)
    document = html5lib.parse(response.content, encoding=response.encoding)
    form = document.find('.//h:form[@id="currentAttempt_form"]', NS)
    if form is None:
        raise ParserError("No <form id=currentAttempt_form>", response)
    fields = (form.findall('.//h:input', NS) +
              form.findall('.//h:textarea', NS))
    data = [
        (field.get('name'), form_field_value(field))
        for field in fields
        if field.get('name')
    ]
    data_lookup = {k: i for i, (k, v) in enumerate(data)}

    def data_get(k, *args):
        if args:
            d, = args
        try:
            return data[data_lookup[k]][1]
        except KeyError:
            if args:
                return d
            raise

    def data_set(k, v):
        try:
            data[data_lookup[k]] = k, v
        except KeyError:
            data_lookup[k] = len(data)
            data.append((k, v))

    def data_extend(kvs):
        for k, v in kvs:
            data_lookup[k] = len(data)
            data.append((k, v))

    data_set('grade', str(grade))
    data_set('feedbacktext', text)
    data_set('gradingNotestext',
             'Submitted with https://github.com/Mortal/bbfetch')

    if rubrics:
        rubric_input = '%s_rubricEvaluation' % attempt_id
        rubric_data_str = data_get(rubric_input)
        rubric_data = json.loads(unquote(rubric_data_str))
        for rubric_cells, rubric in zip(rubrics, rubric_data['rubrics']):
            rubric['client_changed'] = True
            for input_row, row in zip(rubric_cells, rubric['rows']):
                row['cell_id'] = input_row
        rubric_data_str = quote(json.dumps(rubric_data))
        data_set(rubric_input, rubric_data_str)

    files = []

    for i, filename in enumerate(filenames):
        base = os.path.basename(filename)
        data_extend([
            ('feedbackFiles_attachmentType', 'L'),
            ('feedbackFiles_fileId', 'new'),
            ('feedbackFiles_artifactFileId', 'undefined'),
            ('feedbackFiles_artifactType', 'undefined'),
            ('feedbackFiles_artifactTypeResourceKey', 'undefined'),
            ('feedbackFiles_linkTitle', base),
        ])
        with open(filename, 'rb') as fp:
            fdata = fp.read()
        files.append(('feedbackFiles_LocalFile%d' % i, (base, fdata)))
    if is_group_assignment:
        post_url = (
            'https://bb.au.dk/webapps/assignment//gradeGroupAssignment/submit')
    else:
        post_url = (
            'https://bb.au.dk/webapps/assignment//gradeAssignment/submit')
    if not files:
        # Blackboard requires the POST to be
        # Content-Type: multipart/form-data.
        # Unfortunately, requests can only make a form-data POST
        # if it has file-like input in the files list.
        files = [('dummy', io.StringIO(''))]
    try:
        response = session.post(post_url, data=data, files=files)
    except:
        logger.exception("data=%r files=%r", data, files)
        raise
    document = html5lib.parse(response.content, encoding=response.encoding)
    badmsg = document.find('.//h:span[@id="badMsg1"]', NS)
    if badmsg is not None:
        raise ParserError(
            "badMsg1: %s" % element_text_content(badmsg), response,
            'Post data:\n%s' % pprint.pformat(data),
            'Files:\n%s' % pprint.pformat(files))
    msg = document.find('.//h:span[@id="goodMsg1"]', NS)
    if msg is None:
        raise ParserError(
            "No goodMsg1 in POST response", response,
            'Post data:\n%s' % pprint.pformat(data),
            'Files:\n%s' % pprint.pformat(files))
    logger.debug("goodMsg1: %s", element_text_content(msg))
Exemplo n.º 10
0
def submit_grade(session, attempt_id, is_group_assignment,
                 grade, text, filenames, rubrics):
    assert isinstance(session, BlackboardSession)
    if is_group_assignment:
        url = ('https://bb.au.dk/webapps/assignment/' +
               'gradeAssignmentRedirector' +
               '?course_id=%s' % session.course_id +
               '&groupAttemptId=%s' % attempt_id)
    else:
        url = ('https://bb.au.dk/webapps/assignment/' +
               'gradeAssignmentRedirector' +
               '?course_id=%s' % session.course_id +
               '&attempt_id=%s' % attempt_id)
    # We need to fetch the page to get the nonce
    response = session.get(url)
    document = html5lib.parse(response.content, encoding=response.encoding)
    form = document.find('.//h:form[@id="currentAttempt_form"]', NS)
    if form is None:
        raise ParserError("No <form id=currentAttempt_form>", response)
    fields = (form.findall('.//h:input', NS) +
              form.findall('.//h:textarea', NS))
    data = [
        (field.get('name'), form_field_value(field))
        for field in fields
        if field.get('name')
    ]
    data_lookup = {k: i for i, (k, v) in enumerate(data)}

    def data_get(k, *args):
        if args:
            d, = args
        try:
            return data[data_lookup[k]][1]
        except KeyError:
            if args:
                return d
            raise

    def data_set(k, v):
        try:
            data[data_lookup[k]] = k, v
        except KeyError:
            data_lookup[k] = len(data)
            data.append((k, v))

    def data_extend(kvs):
        for k, v in kvs:
            data_lookup[k] = len(data)
            data.append((k, v))

    data_set('grade', str(grade))
    data_set('feedbacktext', text)
    data_set('gradingNotestext',
             'Submitted with https://github.com/Mortal/bbfetch')

    if rubrics:
        rubric_input = '%s_rubricEvaluation' % attempt_id
        rubric_data_str = data_get(rubric_input)
        rubric_data = json.loads(unquote(rubric_data_str))
        for rubric_cells, rubric in zip(rubrics, rubric_data['rubrics']):
            rubric['client_changed'] = True
            for input_row, row in zip(rubric_cells, rubric['rows']):
                row['cell_id'] = input_row
        rubric_data_str = quote(json.dumps(rubric_data))
        data_set(rubric_input, rubric_data_str)

    files = []

    for i, filename in enumerate(filenames):
        base = os.path.basename(filename)
        data_extend([
            ('feedbackFiles_attachmentType', 'L'),
            ('feedbackFiles_fileId', 'new'),
            ('feedbackFiles_artifactFileId', 'undefined'),
            ('feedbackFiles_artifactType', 'undefined'),
            ('feedbackFiles_artifactTypeResourceKey', 'undefined'),
            ('feedbackFiles_linkTitle', base),
        ])
        with open(filename, 'rb') as fp:
            fdata = fp.read()
        files.append(('feedbackFiles_LocalFile%d' % i, (base, fdata)))
    if is_group_assignment:
        post_url = (
            'https://bb.au.dk/webapps/assignment//gradeGroupAssignment/submit')
    else:
        post_url = (
            'https://bb.au.dk/webapps/assignment//gradeAssignment/submit')
    if not files:
        # Blackboard requires the POST to be
        # Content-Type: multipart/form-data.
        # Unfortunately, requests can only make a form-data POST
        # if it has file-like input in the files list.
        files = [('dummy', io.StringIO(''))]
    try:
        response = session.post(post_url, data=data, files=files)
    except:
        logger.exception("data=%r files=%r", data, files)
        raise
    document = html5lib.parse(response.content, encoding=response.encoding)
    badmsg = document.find('.//h:span[@id="badMsg1"]', NS)
    if badmsg is not None:
        raise ParserError(
            "badMsg1: %s" % element_text_content(badmsg), response,
            'Post data:\n%s' % pprint.pformat(data),
            'Files:\n%s' % pprint.pformat(files))
    msg = document.find('.//h:span[@id="goodMsg1"]', NS)
    if msg is None:
        raise ParserError(
            "No goodMsg1 in POST response", response,
            'Post data:\n%s' % pprint.pformat(data),
            'Files:\n%s' % pprint.pformat(files))
    logger.debug("goodMsg1: %s", element_text_content(msg))