Skip to content

Commit

Permalink
Allow admin to enter any class (and refactor data tables to accommoda…
Browse files Browse the repository at this point in the history
…te).
  • Loading branch information
liffiton committed Sep 2, 2024
1 parent a0fdddf commit eb5faf2
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 70 deletions.
50 changes: 28 additions & 22 deletions src/gened/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,30 +154,36 @@ def _get_auth_from_session() -> AuthDict:
""", [auth_dict['user_id']]).fetchall()

found_role = False # track whether the current role from auth is actually found as an active role
if role_rows:
for row in role_rows:
if row['class_id'] == auth_dict['class_id']:
found_role = True
# add class/role info to auth_dict
auth_dict['class_name'] = row['name']
auth_dict['role_id'] = row['role_id']
auth_dict['role'] = row['role']
# check for any registered experiments in the current class
experiment_class_rows = db.execute("SELECT experiments.name FROM experiments JOIN experiment_class ON experiment_class.experiment_id=experiments.id WHERE experiment_class.class_id=?", [auth_dict['class_id']]).fetchall()
auth_dict['class_experiments'] = [row['name'] for row in experiment_class_rows]
elif row['enabled']:
# store a list of any other classes that are enabled (for switching UI)
class_dict: ClassDict = {
'class_id': row['class_id'],
'class_name': row['name'],
'role': row['role'],
}
auth_dict['other_classes'].append(class_dict)

if not found_role:
for row in role_rows:
if row['class_id'] == auth_dict['class_id']:
found_role = True
# add class/role info to auth_dict
auth_dict['role_id'] = row['role_id']
auth_dict['role'] = row['role']
# check for any registered experiments in the current class
experiment_class_rows = db.execute("SELECT experiments.name FROM experiments JOIN experiment_class ON experiment_class.experiment_id=experiments.id WHERE experiment_class.class_id=?", [auth_dict['class_id']]).fetchall()
auth_dict['class_experiments'] = [row['name'] for row in experiment_class_rows]
elif row['enabled']:
# store a list of any other classes that are enabled (for navbar switching UI)
class_dict: ClassDict = {
'class_id': row['class_id'],
'class_name': row['name'],
'role': row['role'],
}
auth_dict['other_classes'].append(class_dict)

if not found_role and not auth_dict['is_admin']:
# ensure we don't keep a class_id in auth if it's not a valid/active one
auth_dict['class_id'] = None

if auth_dict['class_id'] is not None:
# get the class name (after all the above has shaken out)
class_row = db.execute("SELECT name FROM classes WHERE id=?", [auth_dict['class_id']]).fetchone()
auth_dict['class_name'] = class_row['name']
# admin gets instructor role in all classes automatically
if auth_dict['is_admin']:
auth_dict['role'] = 'instructor'

return auth_dict


Expand Down Expand Up @@ -321,7 +327,7 @@ def instructor_required(f: Callable[P, R]) -> Callable[P, Response | R]:
@wraps(f)
def decorated_function(*args: P.args, **kwargs: P.kwargs) -> Response | R:
auth = get_auth()
if auth['role'] != "instructor" and not auth['is_admin']:
if auth['role'] != "instructor":
flash("Instructor login required.", "warning")
return redirect(url_for('auth.login', next=request.full_path))
return f(*args, **kwargs)
Expand Down
9 changes: 8 additions & 1 deletion src/gened/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,19 @@ def switch_class(class_id: int | None) -> bool:
'''Switch the current user to their role in the given class.
Or switch them to no class / no role if class_id is None.
Admin users can switch to any class.
Returns bool: True if user has an active role in that class and switch succeeds.
False otherwise.
'''
auth = get_auth()
user_id = auth['user_id']

# admins can access any class, but we don't bother setting last_class_id for them
if auth['is_admin']:
set_session_auth_class(class_id)
return True

user_id = auth['user_id']
db = get_db()

if class_id:
Expand Down
3 changes: 3 additions & 0 deletions src/gened/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ def user_filter(user_row: Row) -> str:
# on a large instructor view page) yielded no appreciable speedup
# over existing tables.html macro.
#
# NOTE: this is not maintained and is out of sync with the current
# datatable macro.
#
# Usage would be:
# {% set builder = columns | row_builder(edit_handler) %}
# {% for row in data %}
Expand Down
2 changes: 1 addition & 1 deletion src/gened/templates/admin_demo_link.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
{% block admin_body %}
<h1 class="is-size-3">Demo Links <a class="button is-light is-link is-small mt-2" href="{{url_for('admin.demo_link_new')}}">Create New</a></h1>
<div style="max-width: 50em;">
{{ datatable('demo_links', [('id', 'id'), ('name', 'name'), ('expiration', 'expiration'), ('tokens', 'tokens', 'r'), ('enabled', 'enabled', 'r'), ('uses', 'uses', 'r')], demo_links, edit_handler="admin.demo_link_form") }}
{{ datatable('demo_links', [('id', 'id'), ('name', 'name'), ('expiration', 'expiration'), ('tokens', 'tokens', 'r'), ('enabled', 'enabled', 'r'), ('uses', 'uses', 'r')], demo_links, extra_links=[{'text': "edit", 'handler': "admin.demo_link_form", 'param': "id"}]) }}
</div>
{% endblock %}
2 changes: 1 addition & 1 deletion src/gened/templates/admin_experiments.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h1 class="is-size-3">Experiments <a class="button is-light is-link is-small mt-
'experiments',
[('id', 'id'), ('name', 'name'), ('description', 'description'), ('classes', 'count', 'r')],
experiments,
edit_handler="admin.experiment_form",
extra_links=[{'text': "edit", 'handler': "admin.experiment_form", 'param': "id"}],
) }}
</div>
{% endblock %}
3 changes: 2 additions & 1 deletion src/gened/templates/admin_main.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ <h1 class="is-size-3">Consumers <a class="button is-light is-link is-small mt-2"
consumers,
link_col=0,
link_template=filters.template_string('consumer') | safe,
edit_handler="admin.consumer_form"
extra_links=[{'icon': "pencil", 'text': "Edit consumer", 'handler': "admin.consumer_form", 'param': "id"}],
) }}
<h1 class="is-size-3">Classes</h1>
{{ datatable(
Expand All @@ -60,6 +60,7 @@ <h1 class="is-size-3">Classes</h1>
classes,
link_col=0,
link_template=filters.template_string('class') | safe,
extra_links=[{'icon': "admin", 'text': "Administer class", 'handler': "classes.switch_class_handler", 'param': "class_id"}],
) }}
<h1 class="is-size-3">Users</h1>
{{ datatable(
Expand Down
12 changes: 12 additions & 0 deletions src/gened/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,18 @@
</div>
</nav>

{% if auth['is_admin'] and auth['class_id'] is integer and auth['role_id'] is none %}
<div class="has-background-link has-text-light has-text-centered p-2">
<span class="icon-text">
<span class="icon">
<svg aria-hidden="true"><use href="#svg_admin" /></svg>
</span>
Admin in '{{ auth['class_name'] }}' as instructor role.
<button class="button is-rounded is-light is-outlined is-small ml-3" type="button" onclick="location.assign('{{ url_for("classes.leave_class_handler") }}'); return false;">Leave class</button>
</span>
</div>
{% endif %}

{% block second_nav %}{% endblock %}

<main class="site-content">
Expand Down
57 changes: 13 additions & 44 deletions src/gened/templates/tables.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
SPDX-License-Identifier: AGPL-3.0-only
#}

{% macro datatable(name, columns, data, hidden_cols=[], link_col="", link_template=None, edit_handler=None, del_handler=None, csv_link="") -%}
{% macro datatable(name, columns, data, hidden_cols=[], link_col="", link_template=None, extra_links=[], csv_link="") -%}
<style type="text/css">
{% for col in columns %}
{% if col | length > 2 and (col[2] == 'r' or col[2] == 'b') %}
Expand All @@ -14,7 +14,7 @@
}
{% endif %}
{% endfor %}
{% if edit_handler or del_handler %}
{% if extra_links %}
table#tbl_{{name}} tr td:nth-child({{columns | length + 1}}) {
text-align: right;
}
Expand All @@ -31,7 +31,7 @@
{% for col in columns %}
<th {{ "data-hidden=true" if col[0] in hidden_cols else '' }}>{{ col[0] }}</th>
{% endfor %}
{% if edit_handler or del_handler %}
{% if extra_links %}
<th data-sortable="False" data-searchable="False"></th>
{% endif %}
</tr>
Expand All @@ -54,54 +54,23 @@
{%- endif -%}
</td>
{% endfor %}
{% if edit_handler or del_handler %}
{% if extra_links %}
<td>
{% if edit_handler %}
<a class="button is-warning is-small p-1" href="{{ url_for(edit_handler, id=row['id']) }}">Edit</a>
{% endif %}
{% if del_handler %}
<button class="button is-danger is-light is-small p-1" x-on:click="confirm_remove(remove_item, {{row.id}})">Remove</button>
{% endif %}
{% for link in extra_links %}
{% if 'icon' in link %}
<a class="has-text-grey icon icon-text" title="{{ link['text'] }}" href="{{ url_for(link['handler'], **{link['param']: row['id']}) }}">
<svg aria-hidden="true"><use href="#svg_{{ link['icon'] }}" /></svg>
</a>
{% else %}
<a class="button is-warning is-small p-1" href="{{ url_for(link['handler'], **{link['param']: row['id']}) }}">{{ link['text'] }}</a>
{% endif %}
{% endfor %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% if del_handler %}
<dialog id="remove_confirm_dialog" style="width: 75%; min-width: min(32em, 100vw);">
<div class="content box">
<h2>Remove <span x-text="remove_item.type"></span>?</h2>
<ul>
<template x-for="key in Object.keys(remove_item.data)">
<li><span x-text="key"></span>: <span x-text="remove_item.data[key]"></span></li>
</template>
</ul>
<p>Are you sure?</p>
<p x-text="remove_item.extra_text"></p>
<p class="has-text-danger">You will not be able to undo this action.</p>
<form action="{{url_for(del_handler)}}" method="post">
<input type="hidden" name="id" x-bind:value="remove_item.id">
<input type="hidden" name="next" value="{{request.url}}">
<button class="button" type="button" onclick="close_dialog()">Cancel</button>
<button class="button is-link ml-4" type="submit" >Confirm</button>
<form>
</div>
</dialog>
<script type="text/JavaScript">
const dialog = document.querySelector("#remove_confirm_dialog");
function close_dialog() {
dialog.close();
}
function confirm_remove(proxy_item, id) {
proxy_item.id = id;
fetch(`{{url_for(del_handler)}}/${id}`)
.then(response => response.json())
.then(obj => { proxy_item.type = obj.type; proxy_item.data = obj.data; proxy_item.extra_text = obj.extra_text; })
.then(() => dialog.showModal());
}
</script>
{% endif %}
<script type="text/javascript">
{% for col in columns %}
{% if col | length > 2 and col[2] == 'b' %}
Expand Down

0 comments on commit eb5faf2

Please sign in to comment.