"""Room-related UI pages.
This includes the master room list and individual room maps, as well as
room maps specific to a sitting or assessment and sitting. Also includes the
form and handlers for removing a room from a sitting or assessment and
sitting, and for editing the use of a room (reserving/unreserving seats).
"""
import re, time
from operator import attrgetter
from itertools import groupby
from subprocess import Popen, PIPE, STDOUT
from ll.xist.ns import html
from uw.web.wsgi import status
from uw.web.wsgi.delegate import delegate_get_post, delegate_action, delegate_value, always
from uw.web.wsgi.form import use_form_param
from uw.web.wsgi.function import return_html, return_pdf, return_text
from uw.web.pdf.util import handle_pdf_result
from uw.web.html.form import render_select, render_checkbox
from uw.web.html.format import make_table_format, format_email, format_return
from uw.web.html.join import english_join
from .exam_render import render_exam_index
from .room_edit import note_instructions
from .room_render import render_room, render_room_index, room_capacity_columns, room_selected_columns, room_extra_count_columns, room_all_count_columns, render_loading_standard_text, render_href, render_allow_tablet_seats, render_allow_single_seating, render_checkerboard, REMOVE_DIVISION_VALUE
from .ui import render_columns_as_rows, nullif
[docs]def room_from_arc_code (arc, cursor, building, **params):
"""Interpret a URL path arc as a room code.
:param arc: the URL path arc
:param cursor: DB connection cursor
:param building: building information row containing at least building_code
:param params: any additional context not needed for this arc parser
"""
return cursor.room_by_code (building_code=building.building_code, room_code=arc)
[docs]def room_code_delegate (dir_handler, arc_handler):
"""Delegate to URL handler based on room code in URL.
"""
return delegate_value ('room', dir_handler, always (arc_handler),
convert_arc=room_from_arc_code)
[docs]def building_from_arc (arc, cursor, **params):
"""Interpret a URL path arc as a building code.
:param arc: the URL path arc
:param cursor: DB connection cursor
:param params: any additional context not needed for this arc parser
"""
return cursor.building_by_code (building_code=arc)
[docs]def building_delegate (dir_handler, arc_handler):
"""Delegate to URL handler based on building code in URL.
"""
return delegate_value ('building', dir_handler, always (arc_handler),
convert_arc=building_from_arc)
@delegate_get_post
@return_html
def building_index (cursor):
"""Building index URL handler.
:param cursor: DB connection cursor
:return: tuple of (title, list of available buildings, including
links to each one)
:rtype: (string, list)
"""
result = []
buildings = cursor.buildings ()
result.append (html.ul (
html.li (html.a (building, href=building)) for building in buildings
))
return "Building Room Lists (JSON)", result
@delegate_get_post
@return_text
def building_json_list (cursor, building, form):
"""Building JSON room list URL handler.
:param cursor: DB conenction cursor
:param building: building to generate list from
:param form: CGI form results
:return: JSON list of rooms in a building
:rtype: str
Generates a JSON list of rooms in a building. The loading standard to use
for computing capacities is determined from form parameters.
**TODO: should use :func:`uw.web.wsgi.function.return_json`.**
"""
allow_tablet_seats = 'ts' in form
allow_single_seating = 'ss' in form
checkerboard = 'cb' in form
rooms = cursor.rooms_by_building (building_code=building, allow_tablet_seats=allow_tablet_seats, allow_single_seating=allow_single_seating, checkerboard=checkerboard)
return ('[\n' + ',\n'.join (
'{"value": %s, "code": "%s"}' % (room.room_id, room.room_code)
for room in rooms) + '\n]')
@delegate_get_post
@return_text
def room_json_list (cursor, form):
"""JSON room list URL handler.
:param cursor: DB conenction cursor
:param form: CGI form results
:return: JSON list of rooms known to the system
:rtype: str
Generates a JSON list of rooms known to the system. The loading standard
to use for computing capacities is determined from form parameters.
**TODO: should use :func:`uw.web.wsgi.function.return_json`.**
"""
allow_tablet_seats = 'ts' in form
allow_single_seating = 'ss' in form
checkerboard = 'cb' in form
rooms = {}
for k, g in groupby (cursor.rooms_sorted (allow_tablet_seats=allow_tablet_seats, allow_single_seating=allow_single_seating, checkerboard=checkerboard), attrgetter ('building_code')):
rooms[k] = dict ((r.room_code, r) for r in g)
return '{ "codes": [' + ', '.join ('"%s"' % code for code in sorted (rooms.keys ())) + '], "building": {\n' + ',\n'.join ('"%s": { "codes": %s, "room": %s}' % (code, '[' + ', '.join ('"%s"' % code for code in sorted (building.keys ())) + ']', '{\n ' + ',\n '.join ('"%s": { "all_seats": %s, "assignable_seats": %s }' % (r.room_code, r.count_all_seats, r.count_assignable_seats) for r in building.values ()) + '}') for code, building in rooms.items ()) + '}}'
[docs]def render_loading_standard_select (allow_tablet_seats, allow_single_seating, checkerboard):
"""Render links for switching between loading standards.
:param allow_tablet_seats: Whether tablet seats are allowed.
:param allow_single_seating: Whether single-seating is allowed.
:param checkerboard: Whether we are showing the checkerboard usage pattern.
:return: two HTML <p> elements. The first explains the loading standard
currently in effect. The second consists of links with query-only
URLs to change the loading standard. Intended to allow clicking
easily between different loading standard views of a room map.
"""
return [
html.p (
render_loading_standard_text (allow_tablet_seats, allow_single_seating, checkerboard)
),
html.p (
'View with different loading standard: ',
html.a (render_allow_tablet_seats (not allow_tablet_seats),
href=render_href (not allow_tablet_seats, allow_single_seating, checkerboard)),
' ',
html.a (render_allow_single_seating (not allow_single_seating),
href=render_href (allow_tablet_seats, not allow_single_seating, checkerboard)),
' ',
html.a (render_checkerboard (not checkerboard),
href=render_href (allow_tablet_seats, allow_single_seating, not checkerboard))
),
]
@delegate_get_post
@return_html
def room_index (cursor, form, global_roles):
"""Room list URL handler.
:param cursor: DB connection cursor
:param form: CGI form results
:return: tuple of (title, list of all rooms known to the system)
:rtype: (string, list)
Displays a list of all rooms known to the system. Capacities are
computed according to a loading standard determined by form parameters.
"""
result = [format_return ('Main Menu')]
allow_tablet_seats = 'ts' in form
allow_single_seating = 'ss' in form
checkerboard = 'cb' in form
result.append (render_loading_standard_select (allow_tablet_seats, allow_single_seating, checkerboard))
if 'ROOMEDIT' in global_roles:
result.append (html.p (html.a ('Create new room', href='./create')))
result.append (html.p ('Rooms in progress:'))
result.append (render_room_index (cursor.rooms_inactive_sorted (allow_tablet_seats=allow_tablet_seats, allow_single_seating=allow_single_seating, checkerboard=checkerboard), "id/", room_capacity_columns, allow_tablet_seats=allow_tablet_seats, allow_single_seating=allow_single_seating, checkerboard=checkerboard))
result.append (html.p ('Current rooms:'))
result.append (render_room_index (cursor.rooms_sorted (allow_tablet_seats=allow_tablet_seats, allow_single_seating=allow_single_seating, checkerboard=checkerboard), "id/", room_capacity_columns, allow_tablet_seats=allow_tablet_seats, allow_single_seating=allow_single_seating, checkerboard=checkerboard))
return "Room Index", result
@return_pdf
def print_room_handler (cursor, room, form):
"""Room map print handler.
:param cursor: DB connection cursor
:param room: a database row representing a room
:param form: CGI form results
:return: PDF room map
Downloads a PDF room map using the proctor package and displays it
to the user.
"""
allow_tablet_seats = 'ts' in form
allow_single_seating = 'ss' in form
checkerboard = 'cb' in form
params = ['java', 'ca.uwaterloo.odyssey.exams.printBlankRoomMap', str (room.room_id), str (allow_tablet_seats), str (allow_single_seating), str (checkerboard)]
title = '%s-%s_Room.pdf' % (room.building_code, room.room_code)
return handle_pdf_result (params, title,
('There was a problem attempting to generate the proctor package. Please contact ', format_email ('odyssey@uwaterloo.ca'), ' for more assistance.'))
@return_pdf
def room_map_download_pdf (cursor, room, form):
"""Room Map PDF download URL handler.
:param cursor: DB connection cursor
:param room: a database row representing a room
:param form: CGI form results
:return: list of PDF room map and room information
:rtype: list
Serves the room map PDF.
"""
room_map = cursor.execute_optional_tuple ("select * from room_room_map where room_id = %(room_id)s", room_id=room.room_id)
if room_map is None:
raise status.HTTPNotFound ()
return [room_map.room_map_pdf, '%s %s Room Map.pdf' % (room.building_code, room.room_code)]
[docs]def render_room_map_section (room, edit):
"""Displays the room map PDF section
:param room: a database row representing a room
:param edit: a boolean whether a pdf is editable
:return: a list of html elements containing the upload option, if
available, or the download option for a room map PDF
:rtype: list
"""
result = []
if room.room_map_pdf:
result.append (html.table (html.tr (html.td ( html.a ('Download PDF', href="download")))))
if edit:
result.append (html.table (html.tr (html.th ('Room Map PDF: '),
html.td (html.form (html.input (type="file", name="pdf", accept="application/pdf"),
' ',html.input (type="submit", name="!upload", value="Upload!"),
method="post", enctype="multipart/form-data", action="",)))))
return result
@return_html
def room_get_handler (cursor, room, form, global_roles):
"""Room map URL GET handler.
:param cursor: DB connection cursor
:param room: a database row representing a room
:param form: CGI form results
:return: tupel of (room information, map of the relevant room)
:rtype: (string, list)
Displays a map of the relevant room, highlighted according to the seat
designation that would be done if an assessment had the room to itself,
needed the entire room, and used the loading standard determined by the
form parameters.
For rooms not yet activated, will display an edit link for editing the
room map. Also displays an enable and disable buttons depending on the
state of the room's effective date.
"""
result = [format_return ('Main Menu', 'Room Index', None)]
allow_tablet_seats = 'ts' in form
allow_single_seating = 'ss' in form
checkerboard = 'cb' in form
result.append (html.h2 ("Room Note"))
result.append (note_instructions (cursor, room))
if 'ROOMEDIT' in global_roles:
result.append (html.p ("To edit the room note, click ", html.a ('here…', href="../../edit/id/%s/note/" % room.room_id)))
result.append (html.h2 ("Room Map"))
## May edit the room map pdf if you have the global permissions and if the room is current and has no pdf or the room is in editing mode
edit_room_map_pdf = 'ROOMEDIT' in global_roles and ((room.room_current and room.room_map_pdf is None) or room.room_effective is None)
result.append (render_room_map_section (room, edit=edit_room_map_pdf))
result.append (render_loading_standard_select (allow_tablet_seats, allow_single_seating, checkerboard))
result.append (render_columns_as_rows (room, room_capacity_columns))
result.append (render_room (cursor, room, allow_tablet_seats=allow_tablet_seats, allow_single_seating=allow_single_seating, checkerboard=checkerboard))
result.append (html.p (html.a ('Print Room Map…', href="print/" + render_href (allow_tablet_seats, allow_single_seating, checkerboard))))
if 'ROOMEDIT' in global_roles:
if room.room_effective is None:
result.append (html.p ('You may edit this room with ', (html.a ('Edit Room Map…', href="../../edit/id/%s/" % room.room_id), ' or you may enable this room below:')))
result.append (html.form (
html.div (
html.p (
render_checkbox ("enable_room_check"),
' Confirm enable room',
),
html.input (type="submit", name="!enable", value="Enable Room")) if room.room_effective is None else None,
html.p (
html.input (type="submit", name="!supersede", value="Supersede Room"),
' ',
html.input (type="submit", name="!disable", value="Disable Room")) if room.room_current else None,
method="post", action=""
))
return 'Room Information—%s %s' % (room.building_code, room.room_code), result
@return_html
def room_post_handler (cursor, room, form, global_roles):
"""Room map URL POST handler.
:param cursor: DB connection cursor
:param room: a database row representing a room
:param form: CGI form results
Enables or disables a room, setting it's effective date appropriately.
"""
if 'ROOMEDIT' not in global_roles:
raise status.HTTPFound ("")
elif "!enable" in form:
if 'enable_room_check' in form:
cursor.callproc_none ("room_edit_enable", room.room_id)
raise status.HTTPFound ("")
elif "!disable" in form:
cursor.callproc_none ("room_edit_disable", room.room_id)
raise status.HTTPFound ("")
elif "!supersede" in form:
new_room_id = cursor.callproc_required_value ("room_edit_supersede", room.room_id)
raise status.HTTPFound ("../../edit/id/%s/" % new_room_id)
elif "!upload" in form:
map_pdf = form.optional_field_value ("pdf")
if not map_pdf:
return 'Error: No Room Map Supplied', [html.p ('No PDF was submitted. Please go back and try again.')]
cursor.callproc_none ("room_edit_pdf_map", room.room_id, map_pdf)
raise status.HTTPFound ("")
raise status.HTTPBadRequest ()
room_handler = delegate_get_post (room_get_handler, room_post_handler)
@delegate_get_post
@return_pdf
def sitting_room_pdf_handler (cursor, term, admin, roles, sitting, room, exam=None):
"""Display room map for particular sitting.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param sitting: the relevant sitting
:param room: the relevant room
:param exam: the relevant assessment, if any
:return: PDF room map
At present cheats - always shows sitting view of room even if specific
assessment selected, but uses name from assessment.
*** TODO: doesn't actually work due to broken Java class. Either remove
or repair. Proctor package includes this information so possibly not
necessary to have available separately.
"""
params = [str (room.room_id), str (sitting.sitting_id)]
if exam is not None:
params.append (str (exam.exam_id))
return handle_pdf_result (
['java', 'ca.uwaterloo.odyssey.exams.printSittingRoomMap'] + params,
'%s %s %s %s Room Map.pdf' % (admin.admin_description, term.description (), room.building_code, room.room_code),
('There was a problem attempting to generate the room map. Please contact ', format_email ('odyssey@uwaterloo.ca'), ' for more assistance.')
)
@return_html
def sitting_room_html_get_handler (cursor, term, admin, roles, sitting, room):
"""Sitting room display GET URL handler.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param sitting: the relevant sitting
:param room: the relevant room
:return: tuple of (sitting and room information, room map
for relevant sitting)
:rtype: (string, list)
Displays a map of the relevant room in the relevant sitting. If
appropriate, includes form controls for removing assessments from the
room, removing the room from the sitting, and adjusting seating.
"""
result = [format_return ('Main Menu', None, None, 'Offering', None, 'Sitting', None)]
result.append (render_columns_as_rows (room, room_selected_columns))
def delete_render (exam):
if not isinstance (exam, tuple):
# Footer row
if exam.count_allocated_seats:
# Allow unseating/unrushing/removing (see below)
return html.input (type="submit", name="!unseat", value="Unseat/Remove!")
else:
# Allow removing this room from the sitting
return html.input (type="submit", name="!remove", value="Remove Room!")
if exam.sequence_assigned is not None:
return None
boxes = []
if exam.count_occupied_seats > 0:
# Offer to unseat candidates
boxes.append (html.label (
'Unseat %d candidates' % exam.count_occupied_seats,
render_checkbox ("unseat", value=exam.exam_id),
))
if exam.count_used_rush_seats > 0:
# Offer to remove rush seats from use
boxes.append (html.label (
'Remove %d not assigned seats' % exam.count_used_rush_seats,
render_checkbox ("unrush", value=exam.exam_id),
))
# Offer to remove assessment from room
boxes.append (html.label (
'Remove this assessment ',
render_checkbox ("remove", value=exam.exam_id),
))
return english_join (*boxes)
delete_column = (delete_render, 'Action', [])
exams_by_sitting_room = cursor.exams_by_sitting_room (sitting_id=sitting.sitting_id, room_id=room.room_id)
result.append (
html.form (
render_exam_index (cursor, exams_by_sitting_room, "../../exam/",
room_extra_count_columns + (delete_column,), sitting),
method="post", action=""
)
)
result.append (render_room (cursor, room, sitting, form='ISC' in roles))
return '%s: %s %s' % (sitting.full_description, room.building_code, room.room_code), result
seat_code_re = re.compile ("^([0-9]+)-([0-9]+)$")
[docs]def room_update (cursor, term, admin, roles, sitting, room, form, exam=None):
"""Perform updates of information about a room based on form results.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param sitting: the relevant sitting
:param room: the relevant room
:param form: the form results
:param exam: the relevant assessment, if any
Analyzes the form results and makes changes as appropriate, possibly
including:
- allocate or unallocate seats to/from assessments
- reserve or un-reserve seats
- unseat candidates from seats
"""
updates = {}
if exam is None:
allocate = form.optional_field_value ("allocate")
if allocate == 'u':
# Unallocate
updates["exam_id"] = "NULL"
updates["seat_assigned"] = "NULL"
elif allocate is None or allocate == '':
# No change
pass
else:
# Ensure no SQL injection vulnerability
updates["exam_id"] = str (int (allocate))
updates["seat_assigned"] = "NULL"
reserve = form.optional_field_value ("reserve")
if reserve == 'r':
reserve = True
elif reserve == 'u':
reserve = False
else:
reserve = None
if reserve is not None:
updates["seat_reserved"] = str (reserve)
if reserve:
# Must un-designate seats if necessary
updates["seat_assigned"] = "NULL"
unseat = form.optional_field_value ("unseat")
if unseat in ['y']:
updates["seat_in_use"] = "FALSE"
updates["person_id"] = "NULL"
sql = "update exam_sitting_room_seat set %s where (sitting_id, room_id, seat_col, seat_row) = (%%(sitting_id)s, %%(room_id)s, %%(seat_col)s, %%(seat_row)s)" % ', '.join ('%s=%s' % u for u in updates.items ())
division_value = form.optional_field_value ("division_value")
for s in form.multiple_field_value ("s"):
m = seat_code_re.match (s)
if m is not None:
if updates != {}:
cursor.execute_none (sql, sitting_id=sitting.sitting_id, room_id=room.room_id, seat_col=m.group (1), seat_row=m.group (2))
if division_value:
division_seq = form.optional_field_value ("division_seq")
cursor.callproc_none ("exam_sitting_room_seat_division_set", sitting.sitting_id, room.room_id, m.group (1), m.group (2), exam.exam_id, division_seq, nullif (division_value, REMOVE_DIVISION_VALUE))
if exam is not None:
cursor.callproc_none ("exam_assign_designate_seats", exam.exam_id, sitting.sitting_id)
@use_form_param
@return_html
def sitting_room_html_post_handler (cursor, term, admin, roles, sitting, room, form):
"""Sitting room display POST URL handler.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param sitting: the relevant sitting
:param room: the relevant room
:param form: the form results
Implements actions based on form results. These can be any one of:
- Remove an assessment from the room
- Remove the room from the sitting
- Update seat use within the room
Making the actual changes is done by room_update ().
"""
if not 'ISC' in roles:
raise status.HTTPForbidden ()
targeturl = ""
result = []
exams = set ()
if "!unseat" in form:
# Unseat candidates, stop using rush seats, remove assessments from room
for exam_id in form.multiple_field_value ("unseat"):
cursor.execute_none ("update exam_sitting_room_seat set seat_in_use = false, person_id = null where seat_assigned and (sitting_id, room_id, exam_id) = (%(sitting_id)s, %(room_id)s, %(exam_id)s)", sitting_id=sitting.sitting_id, room_id=room.room_id, exam_id=exam_id)
exams.add (exam_id)
for exam_id in form.multiple_field_value ("unrush"):
cursor.execute_none ("update exam_sitting_room_seat set seat_in_use = false where not seat_assigned and (sitting_id, room_id, exam_id) = (%(sitting_id)s, %(room_id)s, %(exam_id)s)", sitting_id=sitting.sitting_id, room_id=room.room_id, exam_id=exam_id)
exams.add (exam_id)
for exam_id in form.multiple_field_value ("remove"):
cursor.execute_none ("update exam_sitting_room_seat set exam_id = null, seat_assigned = null where (sitting_id, room_id, exam_id) = (%(sitting_id)s, %(room_id)s, %(exam_id)s)", sitting_id=sitting.sitting_id, room_id=room.room_id, exam_id=exam_id)
cursor.execute_none ("delete from exam_exam_sitting_room where (sitting_id, room_id, exam_id) = (%(sitting_id)s, %(room_id)s, %(exam_id)s)", sitting_id=sitting.sitting_id, room_id=room.room_id, exam_id=exam_id)
exams.add (exam_id)
elif "!remove" in form:
# Remove this room from list
cursor.execute_none ("delete from exam_sitting_room_seat where (sitting_id, room_id) = (%(sitting_id)s, %(room_id)s)", sitting_id=sitting.sitting_id, room_id=room.room_id)
for exam_id in cursor.execute_values ("delete from exam_exam_sitting_room where (sitting_id, room_id) = (%(sitting_id)s, %(room_id)s) returning exam_id", sitting_id=sitting.sitting_id, room_id=room.room_id):
exams.add (exam_id)
cursor.execute_none ("delete from exam_sitting_room where (sitting_id, room_id) = (%(sitting_id)s, %(room_id)s)", sitting_id=sitting.sitting_id, room_id=room.room_id)
targeturl = "../.."
elif "!update" in form:
room_update (cursor, term, admin, roles, sitting, room, form)
for exam_id in cursor.execute_values ("select exam_id from exam_exam_sitting_room where (sitting_id, room_id) = (%(sitting_id)s, %(room_id)s)", sitting_id=sitting.sitting_id, room_id=room.room_id):
exams.add (exam_id)
for exam_id in exams:
cursor.callproc_none ("exam_assign_designate_seats", exam_id, sitting.sitting_id)
raise status.HTTPFound (targeturl)
sitting_room_handler = delegate_action (delegate_get_post (sitting_room_html_get_handler, sitting_room_html_post_handler), {
'pdf': sitting_room_pdf_handler,
})
@return_html
def exam_sitting_room_html_get_handler (cursor, term, admin, roles, exam, sitting, room):
"""Assessment sitting room map URL GET handler.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param exam: the relevant assessment
:param sitting: the relevant sitting
:param room: the relevant room
:return: tuple of (exam and sitting and room information, room map for
the relevant assessment, sitting, and room)
:rtype: (string, list)
"""
result = [format_return ('Main Menu', None, None, 'Offering', None, 'Assessment', None, 'Sitting', None)]
result.append (render_columns_as_rows (room, room_extra_count_columns))
show_edit_form = exam.sequence_assigned is None and 'ISC' in roles
if show_edit_form:
result.append (html.p (html.a ('Remove this room…', href="../../../../../../sitting/%d/room/%d/" % (sitting.sitting_id, room.room_id))))
result.append (render_room (cursor, room, sitting, exam, form=show_edit_form))
return '%s: %s %s %s' % (exam.full_title, sitting.full_description, room.building_code, room.room_code), result
@use_form_param
@return_html
def exam_sitting_room_html_post_handler (cursor, term, admin, roles, exam, sitting, room, form):
"""Assessment sitting room map URL POST handler.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param exam: the relevant assessment
:param sitting: the relevant sitting
:param room: the relevant room
:param form: the form results
Making the actual changes is done by room_update ().
"""
if not 'ISC' in roles:
raise status.HTTPForbidden ()
room_update (cursor, term, admin, roles, sitting, room, form, exam)
cursor.callproc_none ("exam_assign_designate_seats", exam.exam_id, sitting.sitting_id)
raise status.HTTPFound ("")
exam_sitting_room_handler = delegate_action (
delegate_get_post (exam_sitting_room_html_get_handler, exam_sitting_room_html_post_handler),
{'pdf': sitting_room_pdf_handler})
@delegate_get_post
@return_html
def sitting_room_index_handler (cursor, term, admin, roles, sitting):
"""Display list of rooms used by the sitting.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param sitting: the relevant sitting
:return: tuple of (sitting description, list of
rooms used for the sitting in the assessment)
:rtype: :rtype: (str, list)
"""
result = [format_return ('Main Menu', None, None, 'Offering', None, 'Sitting')]
# sitting unseated candidates
result.append (render_room_index (cursor.rooms_by_sitting (sitting_id=sitting.sitting_id), "", room_all_count_columns))
return '%s Rooms' % sitting.full_description, result
@delegate_get_post
@return_html
def exam_sitting_room_index_handler (cursor, term, admin, roles, exam, sitting):
"""Display list of rooms for the sitting in the assessment.
:param cursor: DB connection cursor
:param term: the relevant term
:param admin: the relevant admin unit
:param roles: the active permissions roles
:param exam: the relevant assessment
:param sitting: the relevant sitting
:return: tuple of (exam title and sitting description, list of
rooms used for the sitting in the assessment)
:rtype: (str, list)
"""
result = [format_return ('Main Menu', None, None, 'Offering', None, 'Assessment', None, 'Sitting')]
# exam-sitting unseated candidates
result.append (render_room_index (cursor.rooms_by_exam_sitting (exam_id=exam.exam_id, sitting_id=sitting.sitting_id), "", room_extra_count_columns))
return '%s: %s Rooms' % (exam.full_title, sitting.full_description), result