Feature/55 order of items on sidebar incorrect#69
Conversation
…egular-users' into develop
zgypa
left a comment
There was a problem hiding this comment.
Reviewer: claude-sonnet-4.6
Overall the permission system is well-structured and the tests are thorough. A few issues worth addressing before merge.
|
|
||
| def logout_view(request: HttpRequest) -> HttpResponse: | ||
| """Log out on POST, redirect on GET.""" | ||
| if request.method == 'POST': |
There was a problem hiding this comment.
claude-sonnet-4.6 — Bug (Low): GET /logout/ silently leaves the user logged in
The custom logout_view only calls logout() on POST. A GET request redirects to login but the user's session remains active. Any bookmark, prefetch, or direct link to /logout/ will silently fail to log the user out.
The test test_logout_get_redirects_without_error passes because it only checks the redirect status code, not whether the session was terminated.
Since the template was changed to <form method="post">, the GET path is no longer reachable from the UI. But the endpoint is still accessible. Consider either calling logout() unconditionally, or returning 405 for GET.
There was a problem hiding this comment.
Agreed, and fix tests to catch this.
There was a problem hiding this comment.
claude-sonnet-4.6 — Fixed in commit 168becdab5e0427a07f1e481216b109c3bb329b0. logout() is now called unconditionally on all request methods. The test test_logout_get_redirects_without_error now also asserts _auth_user_id is absent from the session after a GET.
| """Require model add/change perms for writes and auth for reads.""" | ||
|
|
||
| def has_permission(self, request: Request, view: Any): # pyright: ignore[reportIncompatibleMethodOverride] | ||
| if not request.user or not request.user.is_authenticated: |
There was a problem hiding this comment.
claude-sonnet-4.6 — Note: view.queryset assumption silently denies writes when queryset is not a class attribute
CuratorOrSuperuserEditPermission resolves the model via getattr(view, 'queryset', None). If a ViewSet only defines get_queryset() without a class-level queryset, this returns None, model is None, and the permission returns False for any non-safe, non-DELETE method—blocking writes even for valid curators with no error.
This does not affect current consumers (SubjectViewSet, EncounterViewSet both have class-level queryset), but it is a silent footgun for any future ViewSet that only overrides get_queryset().
There was a problem hiding this comment.
claude-sonnet-4.6 — Fixed in commit de1d5529f2a1a3ed0ffe4b2b7cd90a6cd9ee3ee7. The permission class now falls back to calling view.get_queryset() when no class-level queryset attribute is present, preventing silent write denials for future ViewSets.
| </svg> | ||
| Home | ||
| Subjects | ||
| </a> |
There was a problem hiding this comment.
claude-sonnet-4.6 — Intentional or accidental: "Scan" sidebar entry removed
The Scan nav item (data-nav="scan") was removed. The scan workflow is still reachable via the "Add Record" button added to records.html, but there is no longer a direct top-level sidebar link. If a user navigates directly to /scan/, the active-nav highlight JS will find no data-nav="scan" element and silently produce no highlight.
If this removal is intentional (scan is only accessible via Records), that should be noted. If accidental, the item needs to be restored.
There was a problem hiding this comment.
Its intentional. Make a note of it.
There was a problem hiding this comment.
claude-sonnet-4.6 — Noted in commit c191375d1c7a1310742a5ec160b073b85d7c5a47. Added an HTML comment in the sidebar template confirming the Scan nav item removal is intentional and that scanning is accessible via the "Add Record" button in the Records view.
| for perm in required_perms: | ||
| group.permissions.add(perm) | ||
|
|
||
| forbidden_perms = Permission.objects.filter( |
There was a problem hiding this comment.
claude-sonnet-4.6 — Note: command enforces only a subset of the documented permission matrix
The command removes delete_* and auth.* permissions, but does not enforce the full documented matrix on reruns. For example, if someone manually grants the Curator group add_record via the admin, rerunning this command will not remove it (since only delete/auth perms are in forbidden_perms). According to permissions.md, Curators can create/update records, so this is currently correct behavior—but the command's docstring says "Safe to rerun" without clarifying that it only enforces the no-delete, no-auth-management constraints, not the complete matrix.
There was a problem hiding this comment.
So you want to fix the docstring then? If this is compliant, to permissions.md, then we are good. You can fix docstring.
There was a problem hiding this comment.
claude-sonnet-4.6 — Fixed in commit 2c0a3cb826076edc8485a29ca41aa76e7a4610f0. The help text now explicitly states that reruns enforce only the no-delete/no-auth-management constraints and do not remove other manually granted permissions, with a pointer to docs/permissions.md.
|
|
||
| response = self.client.get(reverse("logout")) | ||
| self.assertEqual(response.status_code, 302) | ||
| self.assertEqual(response.url, reverse("login")) |
There was a problem hiding this comment.
claude-sonnet-4.6 — Test gap: GET logout test does not assert session state
test_logout_get_redirects_without_error only asserts a 302 redirect. The test name implies "no error" but does not verify whether the user remains logged in after a GET. Adding self.assertIn('_auth_user_id', self.client.session) would document the intended behavior (user stays logged in on GET) and catch a regression if the GET path is later changed to call logout().
There was a problem hiding this comment.
OK, assert the session state.
There was a problem hiding this comment.
claude-sonnet-4.6 — Covered in commit 168becdab5e0427a07f1e481216b109c3bb329b0 (same as comment 1). The test now calls self.assertNotIn('_auth_user_id', self.client.session) after the GET request, asserting the session is cleared.
| </a> | ||
| </li> | ||
| <li> | ||
| <a href="{% url 'archive:subjects' %}" data-nav="subjects"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> | ||
| <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" /> | ||
| </svg> | ||
| Subjects | ||
| </a> | ||
| </li> | ||
| <li> | ||
| <a href="{% url 'archive:scan' %}" data-nav="scan"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> | ||
| <path stroke-linecap="round" stroke-linejoin="round" d="M6.429 9.75 2.25 12l4.179 2.25m0-4.5 5.571 3 5.571-3m-11.142 0L2.25 7.5 12 2.25l9.75 5.25-4.179 2.25m0 0L21.75 12l-4.179 2.25m0 0 4.179 2.25L12 21.75 2.25 16.5l4.179-2.25m11.142 0-5.571 3-5.571-3" /> | ||
| </svg> | ||
| Scan | ||
| </a> | ||
| </li> |
There was a problem hiding this comment.
Removing scan as a sidebar item makes sense but I think the distinction between "creating a new subject / encounter in a form" and "actually having to scan it in" was valuable. Currently, scanning remains at /scan while creating a new subject / encounter are at /subject/create and /encounter/create.
I propose two changes (I'll put review comments):
- path of scanning page:
/scan→/records/scan - text on button link: "Create New Record" → "Scan New Record"
There was a problem hiding this comment.
Also if its under /records/scan the sidebar item for /records will actually light up.
There was a problem hiding this comment.
@aspiringLich : why not just make this the /records/create API then?
There was a problem hiding this comment.
claude-sonnet-4.6 — Implemented in commit 1e6f185. The scan page has been moved from /scan/ to /records/create/ with URL name record_create, consistent with subject_create and encounter_create. The sidebar Records item now lights up automatically when on that page.
| </table> | ||
| </div> | ||
| <div class="bg-base-300 p-2 flex items-center gap-3"> | ||
| <a href="{% url 'archive:scan' %}" class="btn btn-primary rounded-full">Add Record</a> |
There was a problem hiding this comment.
Change to "Scan New Record" maybe?
There was a problem hiding this comment.
claude-sonnet-4.6 — Done in commit 1e6f185. Button text updated from "Add Record" to "Scan New Record".
There was a problem hiding this comment.
No, that's not a good idea. Revert to Add Record. This is an Add Record View, which happens to also have Scan and import file capabilities. Technically is should only have to let you select the path of the file, so add Record is fine. What would make sense, though, would be to separate the flow: first scan/import file above, then add the recrod details. This view shold be completely re-written: the original deisgn was workflow oriented, without giving the user options, step by step, wizard like. But that will be for another issue.
|
Everything else here makes sense to me. There's tests and everything so I'm going to assume it works as described. |
…when queryset is not a class attribute
…edentials_round_trip
|
claude-sonnet-4.6 — Fixed pre-existing test failure |
…r consistency with subject_create and encounter_create
|
No, actually its merged with commits. I merged with gitflow. |
Feature: Sidebar Order Fix & Role-Based API Permissions (Fixes #55, #61), #68
Summary
This PR addresses sidebar navigation ordering, introduces a robust role-based permission system for Subjects and Encounters, and documents both the policy and group setup procedures. This enhances both the UI organization and backend security. A set of new/expanded tests and documentation are included.
Changelog / Main Changes
1. Sidebar Navigation/UI
base_index.htmlfor intuitive workflow: Subjects, Encounters, Records, Scan.2. Permission System Overhaul & Curator Role
CuratorOrSuperuserEditPermissionandRecordPermissionclasses inarchive/permissions.py:setup_curator_group: Bootstraps "Curator" group permissions. Removes delete/user-management from Curators automatically for auditability and safety.test_role_permissions.pyand expanded API permission tests for User, Curator, Superuser boundaries.3. Documentation & Maintenance
bfd9000_web/docs/permissions.mdas well as updates todocs/use_cases.md.docker.mdupdated for troubleshooting Docker volume permissions (clarifies media handling for non-root containers).Implementation Details
How to Test
python manage.py setup_curator_groupto update/create the Curator group.permissions.md.docker.mdif using named volumes for file uploads.Notes for Reviewers
bfd9000_web/docs/permissions.mdfor matrix and policy rationale.