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

"""Main menu UI implementation for TA management.

This includes the main menu and the per-term menu.
"""

from io import BytesIO
from zipfile import ZipFile
from operator import attrgetter
from itertools import groupby

from ll.xist.ns import html

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

from uw.web.wsgi.authority import check_authority
from uw.web.wsgi.delegate import delegate_action, delegate_get_post
from uw.web.wsgi.form import use_form_param
from uw.web.wsgi.function import return_html, return_zip
from uw.web.wsgi.status import HTTPFound, HTTPNotFound, HTTPForbidden

from uw.local.dbtools import term_delegate, person_delegate, unit_delegate
from uw.local.termtools import fromCode, current
from uw.local.util.format import format_person

from ....util.identity import use_remote_identity

from ..authority import check_unit_authority
from ..delegate import admin_delegate, taeval_delegate
from ..exam_schedule import make_format_coordinators_column

from .admin import admin_index, admin_handler, admin_misallocated_handler, admin_history
from .entitlement import entitlement_handler, render_entitlement_schedule
from .eval import ta_eval_index_handler, ta_eval_view_handler
from .planner import planner_handler
from .position import position_index_handler, position_edit_handler
from .setup import term_dates_handler, eval_date_handler
from .student import student_index, student_handler, student_misallocated_handler, student_nonstandard_handler, student_tamuo_handler

@return_html
def ta_main_get_index (cursor, unit, roles):
    result = []
    result.append (format_return ('Main Menu'))

    result.append (html.h2 ('Term List'))
    terms = list(map (fromCode, cursor.execute_values ("select term_id from ta_term_unit where term_id > uw_term_from_time (now () - '2 years'::interval) and unit_code = %(unit_code)s order by term_id desc", unit_code=unit.unit_code)))

    result.append (html.ul (html.li (html.a (t.description (), href="%s/" % t.code ())) for t in terms))

    new_term = current () + 1

    if not new_term in terms and 'TAMAN' in roles:
        result.append (html.form (
            html.p (
                html.label (
                    render_checkbox ("confirm"),
                    ' Set up TA management for %s term ' % new_term.description (),
                ),
                render_hidden ("term", new_term.code ()),
                html.input (type="submit", name="!init", value="Initialize!"),
            ),
            method="post", action=""
        ))

    result.append (html.h2 ('Student TA Entitlement Schedule'))

    result.append (html.p ('When TA management is set up for a term, students registered for that term will be granted a TA entitlement according to the following table.  In the event they change plans, adjustments or overrides might be needed depending on the situation.'))

    result.append (render_entitlement_schedule (cursor, unit.unit_code))

    return 'TA Management', result

@use_form_param
@return_html
def ta_main_post_index (cursor, unit, form, roles):
    if not 'TAMAN' in roles:
        raise HTTPForbidden ()

    if "!init" in form:
        if not "confirm" in form:
            return 'Error: no confirmation', [html.p ('Please go back, tick the “Confirm” box, and try again.')]
        term_id = form.required_field_value ("term")
        cursor.callproc_none ("ta_create_term", unit.unit_code, term_id)
        raise HTTPFound ("%s/" % term_id)

ta_main_index = delegate_get_post (ta_main_get_index, ta_main_post_index)

@delegate_get_post
@return_html
def ta_term_index (cursor, unit, term, roles):
    '''TA admin list handler for a particular term

    :param term: Object representing a uw term.
    :param roles: List of user roles.

    :return: an HTML result with links to TA admin pages, TA assignment dates,
    TA evaluation due date, and TA Award Nominations.
    '''
    term_unit = cursor.execute_optional_tuple ("select * from ta_term_unit where (term_id, unit_code) = (%(term_id)s, %(unit_code)s)", term_id=term.code (), unit_code=unit.unit_code)
    if term_unit is None:
        raise HTTPNotFound ()

    result = []
    result.append (format_return ('TA Management'))

    result.append (html.p (html.a ('All Jobs', href="admin/")))
    result.append (html.p (
        html.a ('All Students', href="student/"),
        ' or filter:',
    ))
    result.append (html.ul (
        html.li (
            html.a ('Misallocated', href="student-misallocated"),
            ' — students with more or less work assigned than offered',
        ),
        html.li (
            html.a ('Non-standard Offer', href="student-nonstandard"),
            ' — students with more or less than 1.0 offered',
        ),
    ))

    result.append (html.p (html.a ('Job Planner', href="planner")))
    result.append (html.p (html.a ('Misallocated Jobs', href="position-misallocated/")))

    if 'TAMAN' in roles:
        result.append (html.p (html.a ('Activate/Deactivate Job Positions', href="positions/")))

    result.append (html.p (html.a ('Download Grad Student Photos', href="grad-photos")))

    result.append (html.h2 ('TA Assignment Posting Dates'))

    result.append (html.table (
        html.tr (html.th ('Tentative:'), html.td (str (term_unit.tentative_date))),
        html.tr (html.th ('Final:'), html.td (str (term_unit.finalized_date))),
    ))

    if 'TAMAN' in roles:
        result.append (html.p (html.a ('Edit Dates…', href="edit-dates")))

    result.append (html.h2 ("TA Evaluations"))

    result.append (html.table (
        html.tr (html.th ('Due Date:'), html.td (str (term_unit.eval_due_date))),
        ))

    if 'TAMAN' in roles:
        result.append (html.p (html.a ('Edit Due Date…', href="edit-eval-date")))

        result.append (html.h2 ("Review TA Evaluations"))
        result.append (html.p (html.a ('TA Award Nominations', href="award")))
        result.append (html.p (html.a ('Review Unsatisfactory Evaluations', href="review-un")))
        result.append (html.p (html.a ('Review Evaluation Remarks', href="review")))
        result.append (html.p (html.a ('List of TA Evaluations Not Approved', href="not-approved")))

    return '%s Jobs & Students' % term.description (), result

@use_remote_identity
@return_html
def ta_index (cursor, remote_identity):
    result = []

    result.append (html.p (html.a ('Obtaining Help', href="https://uwaterloo.ca/odyssey/instruct/help"), ' — how to obtain help with this system.'))

    roles = cursor.execute_tuples ("select role_code, unit_code, role_description, unit_description from teaching_admin_unit natural join uw_unit natural join ta_unit natural join auth_admin_personnel_complete natural join auth_role where role_current and person_id = %(person_id)s order by unit_code", person_id=remote_identity.person_id)

    if roles:
        result.append (html.h2 ('My TA Units'))

        for unit_code, unit_roles in groupby (roles, attrgetter ('unit_code')):
            unit_roles = list (unit_roles)
            result.append (html.h3 (html.a (unit_code + ': ' + unit_roles[0].unit_description, href='%s/' % unit_code)))

            result.append (html.p ('You are authorized as:'))
            result.append (html.ul (html.li (role.role_description) for role in unit_roles))
    else:
        result.append (html.p ('You are not authorized for the TA Management System.'))

    return 'TA Main Menu', result

[docs]def render_ta_eval_list (cursor, ta_evals, first_column_name, overall_rating=False): result = [] table_columns = [(first_column_name, lambda t: format_person (cursor, t.person_id))] if overall_rating: table_columns.append (('Overall Rating', lambda t: t.rating)) table_columns.extend ([ ('Evaluation', lambda t: html.a ('View…', href="eval/%s" % t.eval_id)), ('Approved By', lambda t: format_person (cursor, t.eval_approver_person_id)), ('Updated By', lambda t: ((format_person (cursor, updater), html.br ()) for updater in t.updaters)) ]) result.append (make_table_format (*table_columns) (ta_evals)) return result
@return_html def ta_nomination_index (cursor, unit, term, roles): '''Best TA Award list URL handler. :param term: Object representing a uw term. :param roles: List of user roles. :return: an HTML result displaying a list of TA's nominated for the Best TA Award based on TA evaluations with links to view the evaluation. ''' if not 'TAMAN' in roles: raise HTTPForbidden () result = [format_return ('Term')] ta_nominations = cursor.execute_tuples ("select eval_id, person_id, eval_approver_person_id, array_agg (update_person_id order by update_time desc) as updaters from ta_eval_complete natural join person_identity_complete natural left join ta_eval_history where award and (term_id, unit_code) = (%(term_id)s, %(unit_code)s) group by eval_id, person_id, eval_approver_person_id, surname, givennames order by surname, givennames", term_id=term.code (), unit_code=unit.unit_code) if ta_nominations: result.append (render_ta_eval_list (cursor, ta_nominations, 'Nominee')) else: result.append (html.p ("Currently no TA award nominations.")) return 'TA Award Nominations for %s' % term.description (), result @return_html def ta_review_unsatisfactory_index (cursor, unit, term, roles): '''Review Unsatisfactory Evaluations list URL handler. :param unit: A databse row representing a TA unit. :param term: Object representing a uw term. :param roles: List of user roles. :return: an HTML result displaying a list of TA's with unsatisfactory ratings on respective TA evaluations with links to view the evaluation. ''' if not 'TAMAN' in roles: raise HTTPForbidden () result = [format_return ('Term')] ta_evaluations = cursor.execute_tuples ("select eval_id, person_id, eval_approver_person_id, array_agg (update_person_id order by update_time desc) as updaters from ta_eval_complete natural join ta_rating_eval natural join person_identity_complete natural left join ta_eval_history where (rating_code, rating) = ('OVERALL', 2) and remarks is not null and (term_id, unit_code) = (%(term_id)s, %(unit_code)s) group by eval_id, person_id, eval_approver_person_id, surname, givennames order by surname, givennames", term_id=term.code (), unit_code=unit.unit_code) if ta_evaluations: result.append (render_ta_eval_list (cursor, ta_evaluations, 'Teaching Assistant')) else: result.append (html.p ("Currently no unsatisfactory evaluations to review.")) return 'Review Unsatisfactory Evaluations for %s' % term.description (), result @return_html def ta_review_remarks_index (cursor, unit, term, roles): '''Review Unsatisfactory Evaluations list URL handler. :param unit: A databse row representing a TA unit. :param term: Object representing a uw term. :param roles: List of user roles. :return: an HTML result displaying a list of TA's with unsatisfactory ratings on respective TA evaluations with links to view the evaluation. ''' if not 'TAMAN' in roles: raise HTTPForbidden () result = [format_return ('Term')] ta_evaluations = cursor.execute_tuples ("select eval_id, person_id, rating, eval_approver_person_id, array_agg (update_person_id order by update_time desc) as updaters from ta_eval_complete natural join ta_rating_eval natural join person_identity_complete natural left join ta_eval_history where rating_code = 'OVERALL' and remarks is not null and (term_id, unit_code) = (%(term_id)s, %(unit_code)s) group by eval_id, person_id, rating, eval_approver_person_id, surname, givennames order by surname, givennames", term_id=term.code (), unit_code=unit.unit_code) if ta_evaluations: result.append (render_ta_eval_list (cursor, ta_evaluations, 'Teaching Assistant', overall_rating=True)) else: result.append (html.p ("Currently no evaluation remarks to review.")) return 'Review Evaluation Remarks for %s' % term.description (), result @return_html def eval_not_approved_list_handler (cursor, unit, term, roles): '''List of TA Evaluations not Approved URL handler. :param unit: A databse row representing a TA unit. :param term: Object representing a uw term. :param roles: List of user roles. :return: an HTML result displaying a list of TA evaluation admins that are not approved yet. ''' if not 'TAMAN' in roles: raise HTTPForbidden () result = [format_return (dot='Term')] result.append (html.p ('Students will not be able to see their feedback or evaluations until they are approved.')) ta_evaluations = cursor.execute_tuples ("select * from (select unit_code, term_id, admin_id, count(*) filter (where complete) as complete_evaluations, count(*) as total_evaluations from ta_eval_complete where (unit_code, term_id, approved)= (%(unit_code)s, %(term_id)s, False) group by unit_code, term_id, admin_id) as tae natural join teaching_admin left join (select term_id, admin_id, array_agg (person_id order by surname, givennames) filter (where role_code = 'ISC') as isc_person_ids from auth_offering_personnel_complete natural join person_identity_complete where role_code = 'ISC' and role_current and not auth_backup group by term_id, admin_id) as auth_pers using (term_id, admin_id) order by admin_description", unit_code=unit.unit_code, term_id=term.code ()) if ta_evaluations: result.append (make_table_format ( ('Course', lambda r: html.a (r.admin_description, href="../../../term/%s/%s/personnel" % (term.code (), r.admin_id))), ('Completed Evaluations', attrgetter ('complete_evaluations')), ('Total Evaluations', attrgetter ('total_evaluations')), ('Coordinators', make_format_coordinators_column (cursor)), ) (ta_evaluations)) else: result.append (html.p ("Currently no non-approved TA evaluations.")) return 'List of TA Evaluations not Approved for %s for %s' % (unit.unit_description, term.description ()), result @delegate_get_post @return_zip def student_photos (cursor, unit, term, roles): zipname = '%s %s Grad Student Photos' % (term.description (), unit.unit_code) photos = cursor.execute_tuples ("select distinct to_char (uw_id) as uw_id, photo_standard from ta_plan_entitlement natural join std_term_plan natural join std_photo where (term_id, unit_code) = (%(term_id)s, %(unit_code)s) order by uw_id", term_id=term.code (), unit_code=unit.unit_code) result = BytesIO () zip = ZipFile (result, 'a') for photo in photos: zip.writestr ('%s/%s.jpg' % (zipname, photo.uw_id), bytes (photo.photo_standard)) zip.close () return (result.getvalue (), zipname + '.zip') ta_handler = unit_delegate (ta_index, check_authority (check_unit_authority, term_delegate (ta_main_index, delegate_action (ta_term_index, { 'student': person_delegate (student_index, delegate_action (student_handler, { 'eval': taeval_delegate (None, ta_eval_view_handler), 'entitle': entitlement_handler, }) ), 'admin': admin_delegate (admin_index, delegate_action (admin_handler, { '': None, }) ), 'tamuo': student_tamuo_handler, 'admin-history': admin_delegate (admin_history, admin_history), 'grad-photos': student_photos, 'student-misallocated': student_misallocated_handler, 'student-nonstandard': student_nonstandard_handler, 'position-misallocated': admin_misallocated_handler, 'underallocated': None, 'planner': planner_handler, 'edit-dates': term_dates_handler, 'edit-eval-date': eval_date_handler, 'review': delegate_action (ta_review_remarks_index, { 'eval': taeval_delegate (None, ta_eval_view_handler) }), 'review-un': delegate_action (ta_review_unsatisfactory_index, { 'eval': taeval_delegate (None, ta_eval_view_handler) }), 'award': delegate_action (ta_nomination_index, { 'eval': taeval_delegate (None, ta_eval_view_handler), }), 'not-approved': eval_not_approved_list_handler, 'positions': admin_delegate (position_index_handler, position_edit_handler), }) )))