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

"""Course admin unit-related TA assignment display routines.

These are functions related to displaying TA information for course admin units.
"""

from operator import attrgetter
from itertools import groupby

from ll.xist.ns import html

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

from uw.web.wsgi.delegate import delegate_get_post
from uw.web.wsgi.form import use_form_param, parse_form_value
from uw.web.wsgi.function import return_html
from uw.web.wsgi.status import HTTPFound, HTTPForbidden

from uw.local.util.format import format_mailto_url, format_person

from uw.local.termtools import fromCode

from .ui import format_units, format_actual
from ..ui import check_mark

[docs]def format_admins (cursor, admins, roles): result = html.table ( html.colgroup ( html.col (style="width: 13em;"), html.col (style="width: 15em;"), html.col (style="width: 5em;"), html.col (style="width: 10em;"), html.col (style="width: 1%; white-space: nowrap;") if 'TAMAN' in roles else None, html.col (style="width: 10em;"), ), html.tr ( html.th ('Course'), html.th ('Job'), html.th ('Planned'), html.th ('Assigned'), html.th ('Actions') if 'TAMAN' in roles else None, html.th ('Comments'), ) ) planned, assigned = 0, 0 for admin_id, positions in admins: positions = list (positions) is_buyout = len (positions) == 1 and positions[0].job_code == 'BO' and positions[0].person_id != None jobs = [] for p in positions: jobs.append (html.tr ( html.td (p.job_description) if not is_buyout else None, html.td (format_units (p.position_target_units)), html.td (format_actual (p.total_assigned_units, p.position_target_units), html.span (' inactive', style="color: red;") if not p.position_active else None), )) planned += p.position_target_units if p.position_target_units and not is_buyout else 0 assigned += p.total_assigned_units if p.total_assigned_units and not is_buyout else 0 if is_buyout: jobs[0].insert (0, html.td (('Buyout: ', format_person (cursor, p.person_id)), rowspan=len (jobs), colspan=2)) else: jobs[0].insert (0, html.td (html.a (p.admin_description, href="../admin-history/%s/" % p.admin_id), rowspan=len (jobs))) if 'TAMAN' in roles: jobs[0].append (html.td (html.a ('Edit…', href="../admin/%s/" % admin_id), rowspan=len (jobs))) for index, p in enumerate(positions): comment_id = str (admin_id) + "-" + p.job_code divs = [html.div ( html.span (p.position_comment, class_="comment-text", id="comment-text-" + comment_id), html.i (class_="fa fa-pencil comment-toggle comment-edit", id="comment-edit-" + comment_id) if 'TAMAN' in roles else None, class_="comment-display", id="comment-display-" + comment_id)] if 'TAMAN' in roles: divs.append (html.div ( html.input (type="text", class_="comment-input", id="comment-input-" + comment_id, value=p.position_comment), html.i (class_="fa fa-check comment-save", id="comment-save-" + comment_id), html.i (class_="fa fa-times comment-toggle comment-close", id="comment-close-" + comment_id), class_="comment-editable", id="comment-editable-" + comment_id)) jobs[index].append (html.td (html.div (divs))) result.append (jobs) result.append (html.tr ( html.th ('Total (excluding Buyouts)', colspan=2), html.td (format_units (planned)), html.td (format_actual (assigned, planned)), html.td (), html.td () if 'TAMAN' in roles else None, )) return result
@delegate_get_post @return_html def admin_index (cursor, unit, term, roles): result = [format_return ('Term')] if 'TAMAN' in roles: result.append (html.p ('This page shows all jobs which are active for the term and whose course is offered in the term. To active and deactive jobs, use the ', html.a ('Activate/Deactivate Positions', href="../positions/"), ' page. Normally changes to course offerings will be automatically imported from Quest, or may occur as a result of changes in the way course staff are organizing the courses.')) admins = groupby (cursor.execute_tuples ("select * from ta_position_term_relevant_plus natural left join teaching_admin_person where (unit_code, term_id) = (%(unit_code)s, %(term_id)s) order by admin_description, job_description", unit_code=unit.unit_code, term_id=term.code ()), attrgetter ('admin_id')) result.append (format_admins (cursor, admins, roles)) return '%s Jobs' % term.description (), result
[docs]def render_admin_job_details (cursor, unit, term, admin, ems_roles=set (), ta_roles=set (), prefix="", display_prefix=""): """Render job assignments for an offering as HTML. All students assigned jobs with the specified term and admin unit will be listed, along with their positions. If editable is True then form elements will be included for editing the job assignments. Otherwise, the display is intended for clients and will be adjusted appropriately depending on the visibility state of assignments for the term. """ result = [] table_columns = [ (html.col (style="width: 25em;"), 'Student', lambda ta: format_person (cursor, ta.person_id)), (html.col (style="width: 5em;"), 'Units', lambda ta: format_units (ta.assigned_units)), ] if ems_roles: h = html.h3 if cursor.eval_approved (unit_code=unit.unit_code, term_id=term.code (), admin_id=admin.admin_id) or not {'ISC', 'INST', 'ISA'} & ems_roles: name = 'View…' else: name = 'Edit…' table_columns.append ((html.col (style="width: 5em;"), 'Evaluation', lambda ta: (html.a (name, href="%s%d/" % (display_prefix, ta.eval_id)), ' ', (check_mark) if ta.complete else None) if ta.eval_id and {'ISC', 'INST', 'ISA'} & ems_roles else None)) if unit.assignments_finalized: result.append (html.p ('TA assignments have been finalized. Occasional changes may still be necessary. However, you can expect to be informed separately if this happens.')) elif unit.assignments_tentative: result.append (html.p ('TA assignments are still tentative. Assignments are expected to be finalized %s.' % format_date (unit.finalized_date, 'later'))) else: result.append (html.p ('TA assignments are still in progress. Tentative assignments are expected to be ready %s.' % format_date (unit.tentative_date, 'later'))) return result if unit.eval_due: result.append (html.p ("TA evaluations deadline has passed. Best TA Awards have been finalized.")) elif unit.eval_due_date is not None: result.append (html.p ("TA evaluations are due %s" % format_date (unit.eval_due_date))) else: h = html.h2 if 'TAMAN' in ta_roles: name = 'Edit…' else: name = 'View…' table_columns.append ((html.col (style="width: 10em;"), 'Actions', lambda ta: html.a (name, href="../../../%s/student/%d/" % (ta.term_id, ta.person_id)))) jobs = cursor.execute_tuples ("select * from ta_position_term_relevant_plus where (term_id, unit_code, admin_id) = (%(term_id)s, %(unit_code)s, %(admin_id)s) order by job_description", term_id=term.code (), unit_code=unit.unit_code, admin_id=admin.admin_id) links = [] ta_person_ids = [] result.append (links) for job in jobs: tas = cursor.execute_tuples ("select * from ta_position_assignment natural join person_person left join ta_eval_complete using (unit_code, term_id, admin_id, job_code, person_id) where (term_id, unit_code, admin_id, job_code) = (%(term_id)s, %(unit_code)s, %(admin_id)s, %(job_code)s) order by surname, givennames", term_id=term.code (), unit_code=unit.unit_code, admin_id=admin.admin_id, job_code=job.job_code) if job.position_target_units == job.total_assigned_units: units = format_units (job.position_target_units, True) else: units = '%s planned, %s assigned' % (format_units (job.position_target_units, True), format_units (job.total_assigned_units, True)) job_person_ids = [r.person_id for r in tas] ta_person_ids.extend (job_person_ids) email = html.a ('Email all…', href=format_mailto_url (cursor, job_person_ids, admin.admin_description)) h = html.h3 if ems_roles or ta_roles: result.append (h (job.job_description, ' — ', units, ' — ', email)) else: result.append (h (job.job_description, ' — ', units)) if not job.position_active: result.append (html.p ( 'This job is inactive. Either it should be activated', [' on the ', html.a ('Activate/Deactivate Positions', href="%spositions/" % prefix), ' page'] if 'TAMAN' in ta_roles else None, ', or all students should be removed from this job and the job cancelled for the term.' )) if job.position_target_units: result.append (make_table_format (*table_columns, use_colgroup=True) (tas)) if 'TAMAN' in ta_roles: if job.position_active: # Job active, allow editing planned units change_units = [ html.p ( 'Change planned units to ', html.input (type="text", name="units", value=format_units (job.position_target_units)), ), html.p ( 'Update comment: ', html.input (type="text", name="comment", value=job.position_comment), ), html.p ( html.input (type="submit", name="!edit", value="Update Units and Comment!"), ) ] elif not tas: # Allow cancelling job only if no TAs assigned change_units = [ html.p ( 'Update comment: ', html.input (type="text", name="comment", value=job.position_comment), ), html.p ( html.input (type="submit", name="!edit", value="Update Units and Comment!"), ), html.p ( html.input (type="submit", name="!cancel", value="Cancel Job!"), ) ] else: # Job inactive but TAs assigned, must unassign TAs first change_units = ( html.p ( 'Update comment: ', html.input (type="text", name="comment", value=job.position_comment), ), html.p ( html.input (type="submit", name="!edit", value="Update Units and Comment!"), ) ) result.append (html.form ( html.p ( render_hidden ("unit", job.unit_code), render_hidden ("job", job.job_code), change_units ), method="post", action="" )) if ems_roles: # Download link code doesn't really belong in this file but this is # only called from offering page (not editable) and from TA # application (editable) so should be OK. links.append (html.p ( html.a ('Email all…', href=format_mailto_url (cursor, ta_person_ids, admin.admin_description)), '; ', html.a ('Download CSV', href="%s../../talist" % display_prefix), )) return result
@return_html def admin_get_handler (cursor, unit, term, admin, roles): result = [format_return ('Term', 'Course List')] result.append (render_admin_job_details (cursor, unit, term, admin,ta_roles=roles, prefix="../../")) return '%s Job Details for %s' % (term.description (), admin.admin_description), result @use_form_param @return_html def admin_post_handler (cursor, unit, term, admin, form, roles): if not 'TAMAN' in roles: raise HTTPForbidden () if "!edit" in form: job_code = form.required_field_value ("job") units = form.optional_field_value ("units") if units is not None: try: units = parse_form_value (units, float) except ValueError as x: return 'Error: Cannot understand number of units', html.p ( '“%s” does not look like a number of units.' % units ) cursor.callproc_none ("ta_edit_position", unit.unit_code, term.code (), admin.admin_id, job_code, units) comment = form.optional_field_value ("comment") if comment is not None: cursor.callproc_none ("ta_edit_position_comment", unit.unit_code, term.code (), admin.admin_id, job_code, comment) if "!cancel" in form: job_code = form.required_field_value ("job") cursor.callproc_none ("ta_drop_position", unit.unit_code, term.code (), admin.admin_id, job_code) raise HTTPFound ("") admin_handler = delegate_get_post (admin_get_handler, admin_post_handler) @delegate_get_post @return_html def admin_misallocated_handler (cursor, unit, term, roles): result = [format_return ('Term')] admins = [(position.admin_id, position) for position in cursor.execute_tuples ("select * from ta_position_term_relevant_plus natural left join teaching_admin_person where (unit_code, term_id) = (%(unit_code)s, %(term_id)s) and position_target_units is distinct from total_assigned_units order by admin_description, job_description", unit_code=unit.unit_code, term_id=term.code ())] noplan = [] under = [] over = [] for admin_id, position in admins: if position.position_target_units is None: noplan.append ((admin_id, [position])) elif position.position_target_units > position.total_assigned_units: under.append ((admin_id, [position])) else: over.append ((admin_id, [position])) result.append (html.h2 ('No Plan')) result.append (format_admins (cursor, noplan, roles)) result.append (html.h2 ('Underallocated')) result.append (format_admins (cursor, under, roles)) result.append (html.h2 ('Overallocated')) result.append (format_admins (cursor, over, roles)) return '%s Misallocated Jobs' % term.description (), result @delegate_get_post @return_html def admin_history (cursor, unit, term, admin, roles): result = [format_return ('TA Management', 'Term', None)] term_ids = cursor.execute_values ("select term_id from teaching_admin_term where admin_id = %(admin_id)s order by term_id desc", admin_id=admin.admin_id) for term_id in term_ids: hist_term = fromCode (term_id) result.append (html.h2 (hist_term.description ())) ta_term_admin_unit = cursor.execute_optional_tuple ("select ttu.*, unit_description from ta_term_unit_plus ttu natural join uw_unit natural join (select unit_code, term_id from ta_position_term_relevant_plus where (term_id, admin_id) = (%(term_id)s, %(admin_id)s) group by term_id, unit_code) tpt where unit_code = %(unit_code)s", term_id=hist_term.code(), admin_id=admin.admin_id, unit_code=unit.unit_code) if ta_term_admin_unit: result.append (render_admin_job_details (cursor, ta_term_admin_unit, hist_term, admin, display_prefix='../../%s/award/eval/' % hist_term.code ())) else: result.append (html.h3 ("No Assignments for %s" % hist_term.description ())) return '%s Assignment History' % admin.admin_description, result