diff --git a/generic/templates/generic/trainees.html b/generic/templates/generic/trainees.html index 3ca9b4d8..b82260df 100644 --- a/generic/templates/generic/trainees.html +++ b/generic/templates/generic/trainees.html @@ -27,15 +27,15 @@ Supervisor Edit Bulk Edit
- Toggle All {% for trainee in trainees %} - + {{trainee.user.first_name}} {{trainee.user.last_name}} @@ -61,7 +61,10 @@ User/Profile @@ -85,6 +88,7 @@

Bulk edit options

+

Selected trainees: 0

These actions will act on all the selected trainees. Refresh the page to cancel / clear.
@@ -101,15 +105,25 @@ hx-include="[name='selection']" hx-target="#htmx-options" title="Changes the selected users grade." - >Edit grade + +
@@ -203,6 +217,27 @@ lastSorted = {idx, asc}; }); }); + + // Update selection count + const updateSelectedCount = () => { + const checkedCount = table.querySelectorAll('.trainee-checkbox:checked').length; + const countEl = document.getElementById('selected-count'); + if (countEl) { + countEl.textContent = checkedCount; + } + }; + + // Listen for checkbox changes directly + table.addEventListener('change', function(e) { + if (e.target.classList.contains('trainee-checkbox')) { + updateSelectedCount(); + } + }); + + // Expose to window so hyperscript can call it + window.updateSelectedCount = updateSelectedCount; + + updateSelectedCount(); })(); {% endblock %} diff --git a/generic/tests/test_trainees.py b/generic/tests/test_trainees.py index 1bb17491..1d775853 100644 --- a/generic/tests/test_trainees.py +++ b/generic/tests/test_trainees.py @@ -68,3 +68,71 @@ def test_trainee_stuff(client, create_cid_manager, create_users): assert ( len(trainee_rows) == UserProfile.objects.filter(peninsula_trainee=True).count() ) + + +def test_bulk_edit_groups(client, create_cid_manager, create_users): + from generic.models import UserUserGroup + from django.contrib.auth import get_user_model + User = get_user_model() + + client.login(username="cidmanager", password="password") + + group = UserUserGroup.objects.create(name="Test Group") + + user1 = User.objects.get(username="user1") + user2 = User.objects.get(username="user2") + + # Bulk add to group + response = client.post( + reverse("generic:users_bulk_edit"), + data={"selection": [user1.pk, user2.pk]}, + HTTP_HX_REQUEST="true", + HTTP_HX_TRIGGER=f"add-group--{group.id}" + ) + + assert response.status_code == 200 + assert "Group added" in response.content.decode() + assert "alert alert-success" in response.content.decode() + + user1.refresh_from_db() + user2.refresh_from_db() + assert group in user1.user_groups.all() + assert group in user2.user_groups.all() + + # Bulk remove from group + response = client.post( + reverse("generic:users_bulk_edit"), + data={"selection": [user1.pk, user2.pk]}, + HTTP_HX_REQUEST="true", + HTTP_HX_TRIGGER=f"remove-group--{group.id}" + ) + + assert response.status_code == 200 + assert "Group removed" in response.content.decode() + assert "alert alert-success" in response.content.decode() + + user1.refresh_from_db() + user2.refresh_from_db() + assert group not in user1.user_groups.all() + assert group not in user2.user_groups.all() + + +def test_user_not_trainee_htmx(client, create_cid_manager, create_users): + from django.contrib.auth import get_user_model + User = get_user_model() + + client.login(username="cidmanager", password="password") + user2 = User.objects.get(username="user2") + assert user2.userprofile.peninsula_trainee is True + + # Call view via HTMX + response = client.get( + reverse("generic:user_not_trainee", kwargs={"user_id": user2.pk}), + HTTP_HX_REQUEST="true" + ) + + assert response.status_code == 200 + assert response.content == b"" + + user2.refresh_from_db() + assert user2.userprofile.peninsula_trainee is False diff --git a/generic/views.py b/generic/views.py index c55d86c9..d9f76aea 100644 --- a/generic/views.py +++ b/generic/views.py @@ -4916,15 +4916,28 @@ def users_bulk_delete_supervisors(request): u.userprofile.supervisor = None u.save() - modified_users = ",".join([user.username for user in user_models]) + modified_users = ", ".join([f"{user.first_name} {user.last_name}" for user in user_models]) return HttpResponse( - f"Supervisors removed from {modified_users}. Refresh page to see update", - content_type="text/plain", + f""" + + """, + content_type="text/html", ) else: - return HttpResponse("No users selected", content_type="text/plain") + return HttpResponse( + """ + + """, + content_type="text/html", + ) class NoUsersSelected(Exception): @@ -4947,6 +4960,8 @@ def user_not_trainee(request, user_id): user.userprofile.peninsula_trainee = False user.userprofile.save() + if request.htmx: + return HttpResponse("") return HttpResponse( f"{user.username} is no longer a trainee", content_type="text/plain" ) @@ -4968,7 +4983,12 @@ def users_bulk_edit(request): u.is_active = False u.save() - html = "Users deactivated" + html = """ + + """ case "bulk-edit-grade-button": user_grades = UserGrades.objects.all() @@ -5013,7 +5033,12 @@ def users_bulk_edit(request): for u in user_models: u.userprofile.grade_id = grade u.save() - html = "Grades updated" + html = """ + + """ case "bulk-add-group-button": user_groups = UserUserGroup.objects.all() @@ -5064,7 +5089,12 @@ def users_bulk_edit(request): pass u.user_groups.add(r.split("--")[-1]) u.save() - html = "Group added" + html = """ + + """ case r if r.startswith("remove-group"): user_models = get_user_selection_from_request(request) @@ -5072,12 +5102,25 @@ def users_bulk_edit(request): for u in user_models: group_id = r.split("--")[-1] u.user_groups.remove(group_id) - html = "Group removed" + html = """ + + """ case _: html = "None" return HttpResponse(html, content_type="text/html") except NoUsersSelected: - return HttpResponse("No users selected", content_type="text/html") + return HttpResponse( + """ + + """, + content_type="text/html", + ) @user_is_cid_user_manager