"""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),
})
)))