Source code for uw.local.grad.webui.director

"""Director of Admissions interface UI implementation.

This implements the director page, which attempts to show the Director
the information that is relevant to them.
"""

from operator import attrgetter
from itertools import groupby

from ll.xist.ns import html

from uw.web.html.form import render_hidden
from uw.web.html.format import format_return
from uw.web.html.join import html_join
from uw.web.html.bootstrapform import render_bootstrap_submit, parse_bootstrap_date, render_bootstrap_date_selector
from uw.web.wsgi.delegate import delegate_get_post
from uw.web.wsgi.function import return_html
from uw.web.wsgi.form import use_form_param
from uw.web.wsgi import status

from uw.local.util.uw_business_day import non_business_days_in_span

from .util import render_frontpage_header
from ...util.identity import use_remote_identity

from ..db.bulk_unitapp import bulk_app_loader
from ..db.unitapp import UnitApplication

from .list_render import (make_application_link_column, application_list_columns,
                          application_term_column, application_select_column,
                          render_sort_table)

from ..db.status import change_state, render_reason_form, reason_gso_mapping

[docs]def is_director(cursor, remote_identity): return cursor.user_has_role (role_codes=['DIR'], person_id=remote_identity.person_id)
[docs]def make_table_columns (confirm=False): """Assemble table column definitions for application list. :param bool confirm: whether the application list is for the action confirmation screen. If not a column with checkboxes for applications will be included. :return: a list of column definitions for use with :func:`uw.web.html.format.make_table_format`. Compare with :func:`uw.local.grad.webui.faculty.make_table_columns`. Always return the regular application list columns, starting with the link to the individual application, and including the term column. """ table_columns = [make_application_link_column ("../", confirm=confirm)] table_columns.extend (application_list_columns) table_columns.append (application_term_column) if not confirm: table_columns.append(application_select_column) return table_columns
@delegate_get_post @use_remote_identity @return_html def director_index_get_handler (cursor, remote_identity): """Director "front page" GET URL handler. Displays applications for the appropriate Units which are in Initial Review or Circulate (overdue only). These are the applications which the Director should routinely need to consider. """ apps = groupby (cursor.execute_tuples ("select status_description, appl_id from (select distinct on (uw_id, appl_id) * from work_application_staff_lookup natural join work_application_status_current AS wacs natural join work_application_status_code AS wasc natural join std_name_primary where admit_term_id >= uw_term_current () and staff_person_id=%(staff_person_id)s and (status_code in ('INR', 'FAC') or status_code in ('CIR') and deadline < now ())) as t order by status_sequence desc, last_name, first_name, middle_name, uw_id, appl_id", staff_person_id=remote_identity.person_id), attrgetter ('status_description')) circulate_deadline = cursor.execute_required_value ("select default_deadline_date from work_application_status_plus WHERE status_code = 'CIR'") table_columns = make_table_columns () rows = [] for status_description, app2 in apps: appl_ids = tuple (app_tuple.appl_id for app_tuple in app2) app2 = bulk_app_loader (cursor, appl_ids) sorted_app = sorted (app2, key=lambda a: (a.admit_term, a.names)) sort_table = render_sort_table (table_columns, sorted_app, fixed=True) sort_table["class"] += " col-xs-12 table-fixed" table_row = [ html.h3 (status_description), html.div ( sort_table ) ] rows.append (table_row) return 'Your Current Graduate Applications', [ html.div( format_return ('Main Menu'), render_frontpage_header ('their workflow state and deadlines'), html.form( html.div( rows, class_ = "row" ), html.div ( html.div( 'You may select a number of applications and change their states in bulk. To do so, select them using the checkboxes on the far right, then click one of the buttons below: ', class_ = "col-xs-12 bottom-buffer" ), html.div( render_bootstrap_submit ("Deny Requested", name="RJR"), render_reason_form ("reason", cursor, "DENY", class_="form-control input-inline"), class_ = "col-md-6 col-xs-3 text-center" ), html.div( html.div( render_bootstrap_submit ("Circulate", name="CIR"), ' (with a deadline of ', render_bootstrap_date_selector ('date', current=circulate_deadline, months=4, class_="form-control input-inline", disable_days=non_business_days_in_span(cursor, months=4, holidays_only=True)), ') ', ), class_ = "col-md-6 col-xs-8 col-xs-offset-1 col-md-offset-0 text-center" ), class_ = "row top-buffer bottom-buffer" ), method="post", action="" ), class_="main container" ) ] @use_remote_identity @use_form_param @return_html def director_index_post_handler (cursor, remote_identity, form): if not is_director(cursor, remote_identity): raise status.HTTPForbidden () if "RJR" in form or "CIR" in form: appids = [int (i) for i in form.multiple_field_value ("appl_id")] new_state_code = "CIR" if "CIR" in form else "RJR" reason_code = form.optional_field_value ("reason") if new_state_code == "RJR" else None new_state_changes = cursor.execute_tuples ("select * from work_application_status_change where new_status_code = %(code)s and appl_id = ANY(%(appids)s)", code=new_state_code, appids=appids) new_state_info = cursor.execute_optional_tuple ("select * from work_application_status_code where status_code = %(code)s", code=new_state_code) valid_appl_ids = [r.appl_id for r in new_state_changes] # appids that are allowed to change to the given state without override apps = [UnitApplication (cursor, cursor.unitapp_by_id (appl_id=appl_id)) for appl_id in valid_appl_ids] if new_state_info is None: return ('Invalid State', html.p ('There is no state “%s”. Please go back and try again.' % new_state_code)) warning_msg = None if len (valid_appl_ids) != len (appids): invalid_appl_ids = list (set (appids) - set (valid_appl_ids)) links = html_join([html.a (appl_id, href="../view/{}".format (appl_id)) for appl_id in invalid_appl_ids], sep=', ') warning_msg = html.p ("Warning: The following application(s) were ignored because they do not follow the normal workflow (", links, ").") new_state_change = cursor.execute_required_tuple("select default_deadline_date, fixed_deadline from work_application_status_plus WHERE status_code = %(new_state_code)s", new_state_code = new_state_code) if new_state_change.fixed_deadline: deadline = new_state_change.default_deadline_date else: deadline = parse_bootstrap_date (form, "date") if "confirm" in form: for app in apps: change_state (app, remote_identity, new_state_code, deadline, override=False, reason_code=reason_code) else: table_columns = make_table_columns (confirm=True) sort_table = render_sort_table (table_columns, apps) sort_table["class"] += " col-xs-12" deadline_str = "no specified deadline" if deadline is None else "a deadline of {}".format(deadline) reason_str = "" if reason_code: reason_desc = cursor.execute_required_value ("select program_reason_description from uw_program_reason where (program_action_code, program_reason_code) = (%(action_code)s, %(reason_code)s)", action_code=reason_gso_mapping[new_state_code], reason_code=reason_code) reason_str = " for reason {} ({})".format (reason_desc, reason_code) result = html.div( html.form ( html.p ('The following applications will each have their status changed to “{}” with {}{}. The appropriate notifications will be sent out.'.format(new_state_info.status_description, deadline_str, reason_str)), warning_msg if warning_msg else None, html.div( sort_table, class_= "row" ), html.p ( render_hidden ("date", deadline.isoformat ()), render_hidden (new_state_code, new_state_code), render_hidden ("reason", reason_code) if reason_code else None, render_bootstrap_submit ("Confirm", name="confirm", class_="btn-sm") ), method="post", action="" ), class_ = "main container" ) return 'Confirm Bulk State Change', result raise status.HTTPFound ("") director_index_handler = delegate_get_post (director_index_get_handler, director_index_post_handler)