Source code for uw.local.teaching.webui.exam

"""Display individual assessments and lists of assessments.

This implements the pages for viewing an assessment on its own, or as it
relates to a sitting.  It also generates the HTML for the list of assessments
related to a sitting.
"""

from functools import partial
from itertools import count
from operator import attrgetter
from io import BytesIO
from zipfile import ZipFile

from ll.xist.ns import html

from uw.dbtools import copy_to_csv

from uw.web.wsgi.delegate import *
from uw.web.wsgi.form import use_form_param
from uw.web.wsgi.function import return_html, return_pdf, return_zip, return_csv
from uw.web.pdf.util import handle_pdf_result

from uw.web.html.format import make_table_format, format_email, format_return, format_datetime

from uw.local import termtools
from ...util.format import format_person
from ...util.identity import use_remote_identity
from .crowdmark import format_crowdmark_link
from .ui import format_duration
from .exam_render import render_exam, render_exam_index, exam_extra_count_columns, exam_all_count_columns
from .room_render import room_extra_count_columns
from .sitting import write_sitting_proctor_pdf, render_sitting_exam_list
from .sitting_edit import sitting_editor_handler

@delegate_get_post
@return_html
def exam_index (cursor):
    """Assessment list URL handler.

    Displays list of assessments with links to each.
    """
    result = [format_return ('Main Menu')]

    sequence = count (1)

    table = (make_table_format (
        ('#', lambda r: next(sequence), lambda r: {'style': "text-align: right;"}),
        ('Assessment', lambda r: html.a (r.full_title, href="../term/%s/%s/exam/%s/" % (r.term_id, r.admin_id, r.exam_id))),
        ('Assigned', attrgetter ('exam_assigned')),
        ('Start Time', lambda r: format_datetime (r.primary_start_time)),
        ('Duration', lambda r: format_duration (r.exam_duration)),
        ('Integration', partial (format_crowdmark_link, url="dashboard", show_blank=True, brief=True)),
        ('Finalized', lambda r: format_datetime (r.sequence_assigned)),
        ('Masters', attrgetter ('master_count')),
        ('Approved', lambda r: format_datetime (r.master_approved)),
        ('Approver', lambda r: format_person (cursor, r.master_approver_person_id)),
        ('Accepted', lambda r: format_datetime (r.master_accepted)),
    ) (cursor.execute_tuples ("with exam_exam as (select * from exam_exam where primary_start_time > now () - '1 month'::interval) select exam_id, term_id, admin_id, exam_duration, primary_sitting_id, sequence_assigned, master_count, master_approved, master_approver_person_id, master_accepted, exam_assigned, exam_exam_full_title (exam_id) AS full_title, primary_start_time, crowdmark_exam_code, not exam_exam_crowdmark is null as is_crowdmark from exam_exam ee natural left join exam_exam_crowdmark join teaching_admin_term using (term_id, admin_id) where (admin_assigned is not null or exists (select * from exam_exam_master eem where eem.exam_id = ee.exam_id) or not exam_exam_crowdmark is null or exam_assigned) order by primary_start_time, full_title")
    ))
    result.append (table)

    return "Exam Index", result

[docs]def write_mark_entry (cursor, term, admin, exam): """Generate tab-delimited mark entry list for assessment. :param cursor: DB connection cursor. :param exam: the relevant assessment, as a DB result row. Returns a CSV file with the sequence text, version, userid, and UW ID for every copy of the assessment. """ result = copy_to_csv (cursor, "select exam_format_sequence_number (exam_id, exam_sequence) as sequence, exam_exam_master_suffix (exam_id, 'A', questions_version_seq) as version, userid, uw_id from exam_exam_instance natural left join person_identity_complete where exam_id = %d order by sequence, userid" % exam.exam_id) return [result.getvalue (), '%s %s %s Mark Entry List.csv' % (admin.admin_description, term.description (), exam.title)]
@delegate_get_post @return_csv def exam_lookup_handler (cursor, term, admin, roles, exam): """Mark entry list URL handler. Generates a .csv mark entry list for the assessment. """ return write_mark_entry (cursor, term, admin, exam)
[docs]def write_labels_pdf (exam, admin, term): """Obtain a PDF of labels for the assessment. Parameters: exam -- the relevant assessment, as a DB result row; admin -- the relevant admin unit; term -- the relevant term. Invokes the Java printExamLabels class to generate a PDF of candidate labels for the assessment. """ return handle_pdf_result ( ['java', 'ca.uwaterloo.odyssey.exams.printExamLabels', str (exam.exam_id)], '%s %s %s Candidate Labels.pdf' % (admin.admin_description, term.description (), exam.title), ('There was a problem attempting to generate the labels. Please contact ', format_email ('odyssey@uwaterloo.ca'), ' for more assistance.') )
@delegate_get_post @return_pdf def exam_labels_handler (cursor, term, admin, roles, exam): """PDF labels URL handler. Generates PDF assessment candidate labels for the assessment. """ return write_labels_pdf (exam, admin, term)
[docs]def write_folders_pdf (exam, admin, term): """Obtain a PDF of assessment folder inserts for the assessment. Parameters: exam -- the relevant assessment, as a DB result row; admin -- the relevant admin unit; term -- the relevant term. Invokes the Java printExamFolders class to generate a PDF of assessment folder inserts for the assessment. """ return handle_pdf_result ( ['java', 'ca.uwaterloo.odyssey.exams.printExamFolders', str (exam.exam_id)], '%s %s %s Folders.pdf' % (admin.admin_description, term.description (), exam.title), ('There was a problem attempting to generate the folders. Please contact ', format_email ('odyssey@uwaterloo.ca'), ' for more assistance.') )
@delegate_get_post @return_pdf def exam_folders_handler (cursor, term, admin, roles, exam): """PDF folder inserts URL handler. Generates PDF assessment folder inserts for the assessment. """ return write_folders_pdf (exam, admin, term)
[docs]def write_posting_pdf (exam, admin, term, ledger): """Obtain a PDF seating lookup posting list for the assessment. Parameters: exam -- the relevant assessment, as a DB result row; admin -- the relevant admin unit; term -- the relevant term; ledger -- whether ledger size paper (instead of letter) should be used. Invokes the Java printExamLookup class to generate a PDF seating lookup posting list for the assessment. """ return handle_pdf_result ( ['java', 'ca.uwaterloo.odyssey.exams.printExamLookup', 'lg' if ledger else 'lt', str (exam.exam_id)], '%s %s %s Posting List%s.pdf' % (admin.admin_description, term.description (), exam.title, ' (Ledger)' if ledger else ''), ('There was a problem attempting to generate the posting list. Please contact ', format_email ('odyssey@uwaterloo.ca'), ' for more assistance.') )
@use_form_param @delegate_get_post @return_pdf def exam_posting_list_handler (cursor, term, admin, roles, exam, form): """PDF seating lookup posting list URL handler. Generates a PDF seating lookup posting list for the assessment. By default the PDF will be ledger-sized, but letter can be specified by including "lt" in the form results. """ ledger = 'lt' not in form return write_posting_pdf (exam, admin, term, ledger)
[docs]def write_sequence_lookup_pdf (exam, admin, term): """Obtain a PDF of sequence lookup information for the assessment. Parameters: exam -- the relevant assessment, as a DB result row; admin -- the relevant admin unit; term -- the relevant term. Invokes the Java printExamSequenceLookup class to generate a PDF of sequence lookup information for the assessment. """ return handle_pdf_result ( ['java', 'ca.uwaterloo.odyssey.exams.printExamSequenceLookup', str (exam.exam_id)], '%s %s %s Sequence Lookup.pdf' % (admin.admin_description, term.description (), exam.title), ('There was a problem attempting to generate the sequence lookup list. Please contact ', format_email ('odyssey@uwaterloo.ca'), ' for more assistance.') )
@delegate_get_post @return_pdf def exam_sequence_lookup_handler (cursor, term, admin, roles, exam): """PDF sequence lookup information URL handler. Generates PDF sequence lookup information for the assessment. """ return write_sequence_lookup_pdf (exam, admin, term) @delegate_get_post @return_zip def exam_allzip_handler (cursor, term, admin, roles, exam): """Combined assessment .zip URL handler. Generates a .zip of all relevant PDFs for the assessment. """ zipname = '%s %s %s Files' % (admin.admin_description, term.description (), exam.title) result = BytesIO () pdflist = [ write_mark_entry (cursor, term, admin, exam), write_labels_pdf (exam, admin, term), write_folders_pdf (exam, admin, term), write_posting_pdf (exam, admin, term, False), write_posting_pdf (exam, admin, term, True), write_sequence_lookup_pdf (exam, admin, term), ] sittings = cursor.sittings_by_exam (exam_id=exam.exam_id) for sitting in sittings: if sitting.print_proctor_package: pdflist.append (write_sitting_proctor_pdf (term, cursor.sitting_by_id (sitting_id=sitting.sitting_id), exam)) zip = ZipFile (result, 'a') for contents, filename in pdflist: zip.writestr ('%s/%s' % (zipname, filename), contents) zip.close () return (result.getvalue (), zipname + '.zip') @delegate_get_post @use_remote_identity @return_html def exam_handler (cursor, remote_identity, term, admin, roles, exam): """Display overall information about the assessment. """ result = [format_return ('Main Menu', None, None, 'Offering', None)] result.append (render_exam (cursor, remote_identity, exam, roles)) return '%s (%s) %s' % (admin.admin_description, term.description (), exam.title), result @delegate_get_post @use_remote_identity @return_html def sitting_exam_main_handler (cursor, remote_identity, term, admin, roles, sitting, exam): """Display information about the sitting related to the assessment. """ result = [format_return ('Main Menu', None, None, 'Offering', None, 'Sitting', None)] result.append (render_exam (cursor, remote_identity, exam, ['ISC'], sitting)) return '%s: %s' % (sitting.full_description, exam.full_title), result sitting_exam_handler = delegate_action (sitting_exam_main_handler, { 'edit': sitting_editor_handler, })
[docs]def render_admin_exam_list (cursor, term, admin, url_prefix, count=False): """Render a list of assessments for the specified offering. Parameters: cursor -- DB connection cursor; term -- the relevant term; admin -- the relevant admin unit; url_prefix -- the URL prefix for links to the assessments; count -- whether to return the total number of exams as well. If count is False, returns a HTML representation of the assessments for the specified offering, or None if there are none. If count is True, returns the tuple (HTML representation, number of exams), or (None, 0) if there are no exams. """ exams = cursor.exams_by_offering (term_id=term.code (), admin_id=admin.admin_id) if exams: result = render_exam_index (cursor, exams, url_prefix, exam_all_count_columns + room_extra_count_columns) header_row = result[0] result.insert (0, html.tr ( header_row[0], header_row[1], header_row[2], html.th ('Candidates', colspan=len (exam_all_count_columns)), html.th ('Seats', colspan=len (room_extra_count_columns) - 1), header_row[-1], )) for i in [-1, 2, 1, 0]: # Process columns left-to-right so that earlier header_row # column deletions don't interfere with later ones result[0][i]['rowspan'] = 2 del header_row[i] return (result, len (exams)) if count else result else: return (None, 0) if count else None
@delegate_get_post @return_html def exam_index_handler (cursor, term, admin, roles): """Display list of assessments in the offering. """ result = [format_return ('Main Menu', None, None, 'Offering')] exams = render_admin_exam_list (cursor, term, admin, "") if exams: result.append (exams) else: result.append (html.p ('No assessments set up yet.')) return '%s %s Assessments' % (admin.admin_description, term.description ()), result @delegate_get_post @return_html def sitting_exam_index_handler (cursor, term, admin, roles, sitting): """Display list of assessments participating in the sitting. """ result = [format_return ('Main Menu', None, None, 'Offering', None, 'Sitting')] result.append (render_sitting_exam_list (cursor, sitting, "")) return '%s Assessments' % sitting.full_description, result