"""Application display implementation.
This module contains the application search form implementation, as well as the
WSGI structure definition connecting the various application display pages
together.
"""
import re
from datetime import date
from ll.xist.ns import html
from uw.web.html.form import render_select, render_hidden
from uw.web.html.bootstrapform import render_bootstrap_form_row, render_bootstrap_form, render_bootstrap_submit
from uw.web.html.format import make_table_format
from uw.web.wsgi import status
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
from uw.local import termtools
from ...util.identity import use_remote_identity
from ..db import notification
from ..db.unitapp import UnitApplication
from .list_render import make_application_link_column, application_list_columns, application_term_column
from .view_main import view_main_get_handler
from .view_accept import view_accept_get_handler, view_accept_post_handler
from .view_comments import view_comments_get_handler, view_comments_post_handler
from .view_compilation import view_compilation_handler
from .view_contact import view_contact_handler
from .view_offer import view_offer_get_handler, view_offer_post_handler
from .view_history import view_history_handler
from .view_edit import view_edit_get_handler, view_edit_post_handler
ouacNoPattern = re.compile ("^(?P<year>\d{4})-?(?P<number>\d{6})-?0?$")
[docs]def canonicalizeOuacNo (ouac):
"""Canonicalize an OUAC application number.
:param ouac: OUAC application number.
:return: the application number in a fixed format.
The number may be provided with or without hyphens in specific locations,
and with or without the trailing 0. The result will always omit the
hyphens but include the trailing 0.
"""
match = ouacNoPattern.match (ouac)
if match is None:
return None
return match.group ("year") + match.group ("number") + "0"
[docs]def findCurrentWorkApplicationsByOuacNo (cursor, ouac, staff_person_id):
"""Find current applications by OUAC application number.
:param cursor: DB connection cursor.
:param ouac: OUAC application number.
:param staff_person_id: the identity of the person on whose behalf the
search is being conducted.
"""
return cursor.execute_values ("select distinct appl_id from work_application_current_staff_lookup where ouac_reference=%(ouac)s AND staff_person_id=%(staff_person_id)s order by appl_id", ouac=ouac, staff_person_id=staff_person_id)
uwidPattern = re.compile ("^\d{8}$")
[docs]def findCurrentWorkApplicationsByUwid (cursor, uwid, staff_person_id):
"""Find current applications by applicant UW ID.
:param cursor: DB connection cursor.
:param uwid: the UW ID of the student whose applications should be found.
:param staff_person_id: the identity of the person on whose behalf the
search is being conducted.
"""
return cursor.execute_values ("select distinct appl_id from work_application_current_staff_lookup where uw_id=%(uwid)s AND staff_person_id=%(staff_person_id)s order by appl_id", uwid=uwid, staff_person_id=staff_person_id)
[docs]def findCurrentWorkApplicationsByUserid (cursor, userid, staff_person_id):
"""Find current applications by applicant userid.
:param cursor: DB connection cursor.
:param uwid: the userid of the student whose applications should be found.
:param staff_person_id: the identity of the person on whose behalf the
search is being conducted.
"""
return cursor.execute_values ("select distinct appl_id from work_application_current_staff_lookup where userid=%(userid)s AND staff_person_id=%(staff_person_id)s order by appl_id", userid=userid, staff_person_id=staff_person_id)
[docs]def findNamesByPartialName (cursor, names, staff_person_id, limit=None):
"""Returns a tuple of (uw_id, FN, MN, LN, PN) where one of the name components matches one (or more) of the
name fragments seperated by space through a raw case insentitive comparison.
:param cursor: DB connection cursor.
:param names: a string with names seperated by space (e.g. "Marie Anne")
:param staff_person_id: the identity of the person on whose behalf the
search is being conducted.
:param limit: The number of results to return, None means no limit
:returns: A tuple of postgresql result items.
"""
limit_str = "" if limit is None else "LIMIT {}".format(limit)
names = ['%' + name.replace("%", r"\%").replace("*", "%") + '%' for name in names.split()]
return cursor.execute_tuples(("select distinct on (uw_id) uw_id, first_name, middle_name, last_name from std_name "
"join work_application_current_staff_lookup USING (uw_id) where array_to_string(ARRAY[first_name, middle_name, last_name], ' ') "
"ilike ALL(%(names)s) AND staff_person_id=%(staff_person_id)s {}").format(limit_str), staff_person_id = staff_person_id, names = names)
[docs]def findCurrentWorkApplicationsByName (cursor, search, staff_person_id):
"""Find current applications by applicant name.
:param cursor: DB connection cursor.
:param search: part of the name of the student whose applications should be
found; "*" may be used to match any string.
:param staff_person_id: the identity of the person on whose behalf the
search is being conducted.
"""
names = ['%' + name.replace("%", r"\%").replace("*", "%") + '%' for name in search.split()]
return cursor.execute_values ("select distinct appl_id from work_application_search_by_names (%(staff)s, %(names)s)", staff=staff_person_id, names=names)
@use_form_param
@use_remote_identity
@return_html
def view_index_handler (cursor, remote_identity, form):
"""Application search form URL handler.
Displays a search form. If form submission values are present, additional
information is displayed:
- if no applications match, an error message is shown.
- if one application matches, the application redirects to a view of that
application.
- if multiple applications match (typically resulting from a name search),
a list of them is shown.
Note that permissions checking is implicit - if no permissions, no
applications will be found. This could cause some confusion if a former
user or a new but not-yet-authorized user tries to search.
"""
values = {'uwid': None, 'ouacid': None, 'name': None}
errors = dict (values)
values['ouacid'] = "%u-" % date.today ().year
uwid = form.optional_field_value ('uwid')
ouacno = form.optional_field_value ('ouacid')
sname = form.optional_field_value ('name')
if uwid:
# Search by UW ID or Userid
if uwidPattern.match (uwid):
apps = findCurrentWorkApplicationsByUwid (cursor, uwid, remote_identity.person_id)
else:
apps = findCurrentWorkApplicationsByUserid (cursor, uwid, remote_identity.person_id)
values['uwid'] = uwid
if not apps:
errors['uwid'] = "Not found"
elif ouacno and ouacno != values['ouacid']:
# Search by OUAC ID
ouacno = canonicalizeOuacNo (ouacno)
if ouacno is None:
apps = []
else:
apps = findCurrentWorkApplicationsByOuacNo (cursor, ouacno, remote_identity.person_id)
values['ouacid'] = ouacno
if not apps:
errors['ouacid'] = "Not found"
elif sname:
# Search by name
sname = sname.lower ()
apps = findCurrentWorkApplicationsByName (cursor, sname, remote_identity.person_id)
values['name'] = sname
if not apps:
errors['name'] = "Not found"
else:
# Blank form
apps = []
if len (apps) == 0:
return render_search_form (cursor, values, errors, None)
elif len (apps) == 1:
raise status.HTTPFound ("%s/" % apps[0])
else:
return render_search_form (cursor, values, errors, apps)
view_handler = use_remote_identity (delegate_action (delegate_get_post (view_main_get_handler, view_comments_post_handler), {
'contact': view_contact_handler,
'comments': view_comments_get_handler,
'offer': delegate_get_post (view_offer_get_handler, view_offer_post_handler),
'history': view_history_handler,
'edit': delegate_get_post (view_edit_get_handler, view_edit_post_handler),
'accept': delegate_get_post (view_accept_get_handler, view_accept_post_handler),
'compilation': view_compilation_handler,
}))