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

"""Assessment candidate special case UI.

This implements the pages which allow course staff to view special cases and
select/unselect special-case candidates from an assessment.
"""

import re
from operator import attrgetter

from ll.xist.ns import html

from uw.web.html.form import render_hidden
from uw.web.html.format import make_table_format, format_return, format_datetime

from uw.web.wsgi.delegate import *
from uw.web.wsgi.form import use_form_param
from uw.web.wsgi.function import return_html

from uw.local.util.format import format_person
from uw.local.util.identity import use_remote_identity

from .ui import format_candidate_list
from .exam_edit import exam_may_edit_seat

@delegate_get_post
@return_html
def exam_special_case_index_handler (cursor, term, admin, roles, exam):
    """Display all special candidate cases for the assessment.

    :param cursor: DB connection cursor
    :param term: the relevant term
    :param admin: the relevant admin unit
    :param roles: the active permissions roles
    :param exam: the relevant assessment, if any
    :return: tuple of (description, special candidate cases)
    :rtype: (str, list)

    This means all candidates who are not writing in the primary sitting or who
    are extra (typically INC-writing) candidates.  The resulting page consists
    of a table with one row per candidate.  If the current user is authorized
    as an ISC according to the roles, then form controls to select candidates
    for de-selection and a link to a form for adding special candidates are
    provided.
    """
    result = [format_return ('Main Menu', None, None, 'Offering', None, 'Assessment')]

    candidates = cursor.execute_tuples ("select person_id, sitting_id, userid, uw_id::integer, surname, givennames, start_time, admin_description as sitting_admin_description, extra_candidate, candidate_reserved, candidate_assigned, building_code, room_code, seat_code from accom_candidate_sitting_plus natural join person_identity_complete natural left join (exam_sitting join teaching_admin on (admin_id=sitting_admin_id)) left join (select * from room_room_plus where room_current) as rrp using (room_id) natural left join exam_sitting_room_seat_extra where exam_id=%(exam_id)s and (extra_candidate or not unselected) order by start_time, sitting_admin_description, surname, givennames, uw_id", exam_id=exam.exam_id)

    if 'ISC' in roles and not exam.master_accepted:
        extra_columns = [('Unselect', lambda r: html.a ('Unselect…', href="edit?unselect=%s" % r.person_id) if r.extra_candidate else None)]
    else:
        extra_columns = []
    result.append (format_candidate_list (cursor, candidates, extra_columns=extra_columns))

    if 'ISC' in roles and not exam.master_accepted:
        result.append (html.p (html.a ('Select Extra Candidates…', href="edit")))
        result.append (html.p (html.a ('Update Candidates', href="../edit-candidate")))

    return '%s (%s) %s: Special Cases' % (admin.admin_description, term.description (), exam.title), result

@return_html
def exam_special_case_edit_get_handler (cursor, term, admin, roles, exam, form):
    """Special case candidate editing form GET handler.

    :param cursor: DB connection cursor
    :param term: the relevant term
    :param admin: the relevant admin unit
    :param roles: the active permissions roles
    :param exam: the relevant assessment, if any
    :param form: CGI form results
    :return: tuple of (description, HTML for editing form)
    :rtype: (str, list)

    This is either a box in which to type or paste the identities of some
    extra students to be selected for the assessment, or a presentation of
    an already-selected student to be verified for un-selection from the
    assessment, depending on form parameters.
    """
    if not 'ISC' in roles:
        raise status.HTTPForbidden ()

    result = []

    if "unselect" in form:
        result.append (html.form (
            html.p ('The following candidate will be unselected from this assessment:'),
            format_candidate_list (cursor, cursor.exam_candidate_details_ordered (exam_id=exam.exam_id, candidates=[int (form.required_field_value ("unselect"))])),
            html.p (html.input (type="submit", name="!unselect", value="Unselect!")),
            method="post", action=""
        ))

        return '%s (%s) %s: Unselect Extra Candidates' % (admin.admin_description, term.description (), exam.title), result
    else:
        result.append (html.form (
            html.p ('Paste or type some candidates, one per line.'),
            html.p ('Each line must include the UW student ID and userid separated by either a space, comma, or tab.  The userid may be all uppercase or all lowercase but not mixed case, and must be a maximum of 8 characters:'),
            html.textarea (name="candidates", rows=15, cols=40),
            html.p (html.input (type="submit", name="!review-add", value="Next…")),
            method="post", action=""
        ))

        return '%s (%s) %s: Select Extra Candidates' % (admin.admin_description, term.description (), exam.title), result

[docs]def process_candidate_list (cursor, candidates): """Scan lines of text for candidate identities. :param cursor: DB connection cursor :param candidates: lines of text to search for candidate identities :return: list of candidate identities :rtype: list This routine will recognize one candidate per line, assuming that it can find a UW ID and a userid which match. """ uw_id_re = re.compile ('(^|[^0-9])([0-9]{8})([^0-9]|$)') re_str = '(^|[^a-z0-9-])([a-z][a-z0-9-]{1,7})([^a-z0-9-]|$)' userid_re = re.compile (re_str + '|' + re_str.upper ()) def process_candidate (candidate): m = re.search (uw_id_re, candidate) if m is None: return None n = re.search (userid_re, candidate) if n is None: return None return cursor.execute_optional_tuple ("select uw_id, person_id from person_identity_complete where (uw_id, userid) = (%(uw_id)s, %(userid)s)", uw_id=m.group (2), userid=(n.group (2) or n.group(5)).lower ()) return list(map (process_candidate, candidates))
@use_remote_identity @return_html def exam_special_case_edit_post_handler (cursor, remote_identity, term, admin, roles, exam, form): """Special case candidate editing form POST handler. :param cursor: DB connection cursor :param term: the relevant term :param admin: the relevant admin unit :param roles: the active permissions roles :param exam: the relevant assessment, if any :param form: CGI form results :return: tuple of (description, HTML elements) if reviewing list or selecting candidates OR None if unselecting candidates :rtype: (str, list) or None Depending on form parameters, this may do any of the following: - Allow the user to review a list of potential candidates to be selected for an assessment; - Select candidates for an assessment; or - Unselect special candidates from an assessment. """ if not 'ISC' in roles: raise status.HTTPForbidden () if exam.master_accepted: raise status.HTTPFound (".") result = [] if "!review-add" in form: given = form.optional_field_value ("candidates") if given is None: result.append (html.p ('No candidates specified.')) else: given = given.splitlines () people = process_candidate_list (cursor, given) found = sum (p is not None for p in people) if found == len (given): result.append (html.p ('%s %s found:' % (found, 'person' if found == 1 else 'people'))) else: result.append (html.p ('%s lines given, %s %s found:' % (len (people), found, 'person' if found == 1 else 'people'))) table = html.table (html.tr ( html.th ('Given'), html.th ('UW ID'), )) for g, p in zip (given, people): if p is None: cells = html.td (colspan=2) else: cells = ( html.td ( render_hidden ("c", str (p.person_id)), format_person (cursor, p.person_id) ), ) table.append (html.tr (html.td (g or ' '), cells)) result.append (html.form ( table, html.p (html.input (type="submit", name="!add", value="Select %s Candidate%s!" % (found, '' if found == 1 else 's'))), method="post", action="" )) return '%s (%s) %s: Select Extra Candidates' % (admin.admin_description, term.description (), exam.title), result elif "!add" in form: result = [format_return ('Main Menu', None, None, 'Offering', None, 'Assessment', dot='Special Cases')] candidates = [int (c) for c in form.multiple_field_value ("c")] request_id = cursor.callproc_required_value ("accom_create_exam_request", exam.exam_id, remote_identity.person_id, candidates) cursor.execute_none ("update accom_request_candidate set extra_candidate = True where (term_id, accom_admin_id, request_id, exam_id) = (%(term_id)s, %(accom_admin_id)s, %(request_id)s, %(exam_id)s) and person_id = any (%(candidates)s)", term_id=exam.term_id, accom_admin_id=admin.admin_id, request_id=request_id, exam_id=exam.exam_id, candidates=candidates) cursor.callproc_none ("accom_request_completed", exam.term_id, admin.admin_id) result.append (html.p ('The following %s individual%s been selected for this assessment:' % (len (candidates), ' has' if len (candidates) == 1 else 's have'))) result.append (format_candidate_list (cursor, cursor.exam_special_candidate_details_ordered (exam_id=exam.exam_id, candidates=candidates))) cursor.callproc_none ("exam_assign_designate_seats", exam.exam_id) return '%s (%s) %s: Select Extra Candidates' % (admin.admin_description, term.description (), exam.title), result elif "!unselect" in form: candidates = [int (c) for c in form.multiple_field_value ("c")] request_id = cursor.callproc_required_value ("accom_create_exam_request", exam.exam_id, remote_identity.person_id, candidates) cursor.execute_none ("update accom_request_candidate set extra_candidate = false where (term_id, accom_admin_id, request_id, exam_id) = (%(term_id)s, %(accom_admin_id)s, %(request_id)s, %(exam_id)s) and person_id = any (%(candidates)s)", term_id=exam.term_id, accom_admin_id=admin.admin_id, request_id=request_id, exam_id=exam.exam_id, candidates=candidates) cursor.callproc_none ("accom_request_completed", exam.term_id, admin.admin_id) cursor.callproc_none ("exam_assign_designate_seats", exam.exam_id) raise status.HTTPFound (".") exam_special_case_handler = delegate_action (exam_special_case_index_handler, { 'edit': use_form_param (delegate_get_post (exam_special_case_edit_get_handler, exam_special_case_edit_post_handler)), })