diff --git a/generic/filters.py b/generic/filters.py index d2e883e0..0703b24c 100644 --- a/generic/filters.py +++ b/generic/filters.py @@ -144,6 +144,16 @@ class SupervisorFilter(django_filters.FilterSet): "site": ["exact"], } + @property + def qs(self): + parent = super().qs + + if "active" not in self.request.GET: + return parent.filter(active=True) + + return parent + + def __init__( self, data=None, diff --git a/generic/templates/generic/supervisor_base.html b/generic/templates/generic/supervisor_base.html index a02114a4..1e4d5022 100755 --- a/generic/templates/generic/supervisor_base.html +++ b/generic/templates/generic/supervisor_base.html @@ -1,10 +1,23 @@ {% extends 'generic/base.html' %} {% block navigation %} -{{block.super}} -
-Supervisors: View all -Create new + {{ block.super }} +
+
+ +
+
{% endblock %} {% block js %} diff --git a/generic/templates/generic/supervisor_form.html b/generic/templates/generic/supervisor_form.html index 58f873da..32dacbd2 100644 --- a/generic/templates/generic/supervisor_form.html +++ b/generic/templates/generic/supervisor_form.html @@ -1,34 +1,42 @@ {% extends "generic/supervisor_base.html" %} - {% load crispy_forms_tags %} -{% block css %} - - -{% endblock %} -{% block js %} - -{{form.media}} - - - - -{% endblock %} {% block content %} -

Edit Supervisor / {{object.cid}}

-

Use this form to create / edit a supervisor

-

Trainees can be assigned to a supervisor by editing the trainee and selecting the supervisor from the drop down list.

-{% if errors %} - -{% endif %} -
- {% csrf_token %} +
+
+
+
+

+ {% if object %} + Edit Supervisor + {% else %} + Create Supervisor + {% endif %} +

+

+ Use this form to manage the supervisor account details. Trainees can be assigned to a supervisor by editing the trainee's profile. +

- - {{ form|crispy }} -
- - + {% if errors %} + + {% endif %} + +
+ {% csrf_token %} + {{ form|crispy }} +
+ + + Cancel + +
+
+
+
+
+
{% endblock %} \ No newline at end of file diff --git a/generic/templates/generic/supervisor_list.html b/generic/templates/generic/supervisor_list.html index 6fde21d7..ca0618e8 100755 --- a/generic/templates/generic/supervisor_list.html +++ b/generic/templates/generic/supervisor_list.html @@ -1,24 +1,47 @@ {% extends 'generic/supervisor_base.html' %} {% load render_table from django_tables2 %} + {% block content %} +
+
+

Supervisors

+

+ Manage registered course and educational supervisors, hospital sites, and trainees. +

+
+
+ + Showing: {{ table.rows|length }} + +
+
-

Supervisors

+
+
+
+ + {% render_table table %} + +
+
+
- {% render_table table %} - -{% comment %} {% endcomment %} {% include "generic/partials/filter_bar.html" with filter=filter app_name=app_name collapse_id="bottom-filter-body" %} - {% endblock %} {% block js %} {% endblock %} \ No newline at end of file diff --git a/rad/tests/test_user_access.py b/rad/tests/test_user_access.py index 4bcf3580..fa23cc63 100644 --- a/rad/tests/test_user_access.py +++ b/rad/tests/test_user_access.py @@ -140,3 +140,46 @@ def test_cid_manager_can_send_password_reset_from_account_profile(client, create assert mail.outbox[0].to == ["managed@example.com"] +@pytest.mark.django_db +def test_people_page_and_user_search(client, create_users, create_cid_manager): + # Log in as cid_user_manager + client.force_login(create_cid_manager) + + # 1. Test standard GET request to /people page + response = client.get(reverse("people")) + assert response.status_code == 200 + assert b"Quick User Search" in response.content + assert b"hx-indicator=\"#search-indicator\"" in response.content + + # 2. Test HTMX GET request with empty query + response_empty = client.get( + reverse("people"), + {"q": ""}, + HTTP_HX_REQUEST="true", + ) + assert response_empty.status_code == 200 + assert response_empty.content == b"" + + # 3. Test HTMX GET request with matching query + user1, user2 = create_users + response_search = client.get( + reverse("people"), + {"q": "user1"}, + HTTP_HX_REQUEST="true", + ) + assert response_search.status_code == 200 + assert b"user1" in response_search.content + assert b"user1@user.com" in response_search.content + assert b"Actions" in response_search.content + + # 4. Test HTMX GET request with no matching users + response_no_match = client.get( + reverse("people"), + {"q": "nonexistent_username_xyz"}, + HTTP_HX_REQUEST="true", + ) + assert response_no_match.status_code == 200 + assert b"No users found matching" in response_no_match.content + + + diff --git a/rad/views.py b/rad/views.py index 249d94d5..5921fb72 100644 --- a/rad/views.py +++ b/rad/views.py @@ -140,9 +140,40 @@ def server(request): @user_is_cid_user_manager def people(request): + """Render the people management overview or search users via HTMX. + + Scope: managers and superusers. + Functionality: provides details on user roles/groups, and a single search + input that filters users dynamically via HTMX GET requests. + """ + if request.htmx: + q = request.GET.get("q", "").strip() + if not q: + return HttpResponse("") + + users = ( + User.objects.filter( + Q(first_name__icontains=q) + | Q(last_name__icontains=q) + | Q(email__icontains=q) + | Q(username__icontains=q) + ) + .prefetch_related( + "userprofile", "userprofile__grade", "userprofile__supervisor", "user_groups" + ) + .order_by("first_name", "last_name", "username")[:20] + ) + + return render( + request, + "rad/partials/_people_search_results.html", + {"users": users, "q": q}, + ) + return render(request, "people.html", {}) + @login_required def profile(request): """Render the current user's profile page with group management context. diff --git a/templates/people.html b/templates/people.html index 288f534c..0a296b73 100644 --- a/templates/people.html +++ b/templates/people.html @@ -13,6 +13,31 @@ Trainees can be viewed here ( {% endfor %} ) +
+
+

Quick User Search

+
+
+
+ + +
+
+
+ +
+
+
+
+
+

People

The system contains a number of different people / groups. This affects how they are managed and what they are able to do.

They can be managed using the navigation links above

diff --git a/templates/rad/partials/_people_search_results.html b/templates/rad/partials/_people_search_results.html new file mode 100644 index 00000000..644b3435 --- /dev/null +++ b/templates/rad/partials/_people_search_results.html @@ -0,0 +1,71 @@ +{% if users %} +
+ + + + + + + + + + + + + {% for user in users %} + + + + + + + + + {% endfor %} + +
UserGradeEmailSupervisorGroupsActions
+
+ {{ user.first_name }} {{ user.last_name }} + + + +
+ {{ user.username }} +
+ {% if user.userprofile.grade %} + {{ user.userprofile.grade }} + {% else %} + No grade + {% endif %} + + {{ user.email|default:"—" }} + + {% if user.userprofile.supervisor %} +
+ {{ user.userprofile.supervisor }} + + + +
+ {% else %} + + {% endif %} +
+ {% for group in user.user_groups.all %} + {{ group.name }} + {% empty %} + None + {% endfor %} + + +
+
+
Showing up to 20 results for "{{ q }}".
+{% else %} +
+ No users found matching "{{ q }}". +
+{% endif %}