Summary
An audit of the hold list permission logic reveals a patchwork of inconsistent checks across views, services, and templates. Ownership, lock status, and role gates are applied differently depending on which view handles the request, and several views that exist have no corresponding UI affordance.
Current state
| View |
Role gate |
Ownership check |
Lock check |
Template affordance |
| holdlist_list |
None |
— |
— |
N/A |
| holdlist_detail |
None (computes flags) |
— |
— |
N/A |
| holdlist_create |
_require_holdlist_write |
N/A |
N/A |
Link in list view |
| holdlist_edit |
Inline |
Creator OR admin/mgr |
Yes |
Edit button (gated) |
| holdlist_delete |
Inline |
Creator OR admin/mgr |
None |
No button |
| holdlist_add_item |
_require_holdlist_write |
None |
Service layer |
Add form (gated) |
| holdlist_remove_item |
_require_holdlist_write |
None |
Service layer |
Remove button (gated) |
| holdlist_edit_item |
_require_holdlist_write |
None |
None |
No button |
| holdlist_update_pull_status |
_require_holdlist_write |
None |
None |
Pull buttons (shown on locked lists) |
| holdlist_lock |
_require_holdlist_write |
None |
— |
No button |
| holdlist_unlock |
_require_holdlist_write |
None |
— |
No button |
| holdlist_fulfil |
_require_holdlist_write |
None |
None |
No button |
| holdlist_pick_sheet |
None |
None |
— |
PDF link (ungated) |
Problems
1. Inconsistent ownership checks
holdlist_edit and holdlist_delete require the user to be the creator (or admin/department_manager). But holdlist_lock, holdlist_unlock, holdlist_fulfil, and all item-mutation views only check the role — any member can lock, unlock, fulfil, or mutate items on any hold list, even ones they didn't create.
Decision needed: Should members be able to collaborate on any hold list (current behaviour for items), or should destructive/state-changing operations (lock, unlock, fulfil, delete) require ownership like edit does?
2. Missing lock checks
holdlist_edit_item — can edit item quantity/notes on a locked list
holdlist_update_pull_status — can change pull status on a locked list
holdlist_delete — can delete a locked hold list
holdlist_fulfil — can bulk-checkout from a locked list
add_item and remove_item correctly delegate lock enforcement to the service layer, but the other views don't check at all — neither in the view nor in any service call.
3. Missing template affordances
These views exist and are routed but have no corresponding button, link, or form in holdlist_detail.html:
- Lock / Unlock — no toggle button
- Delete — no delete button
- Fulfil — no fulfil/checkout button
- Edit Item (quantity/notes) — no inline edit control
Users can only reach these via direct URL, which means they're effectively invisible.
4. Pull status buttons shown on locked lists
The template renders "Mark Pulled" / "Mark Unavailable" / "Reset" buttons gated by can_write_holdlist but not by hold_list.is_locked. A member sees actionable buttons on a locked list, and the view will happily process the POST.
5. _require_holdlist_write is too thin
The helper only checks role membership. Every view that needs ownership or lock enforcement does it inline (or doesn't). This leads to the inconsistencies above.
Proposal: Extend the helper or create a richer permission function, e.g.:
def check_holdlist_permission(user, hold_list=None, *, require_owner=False, require_unlocked=False):
role = get_user_role(user)
if role not in HOLD_LIST_WRITE_ROLES:
raise PermissionDenied
if require_owner and hold_list:
if hold_list.created_by != user and role not in ("system_admin", "department_manager"):
raise PermissionDenied
if require_unlocked and hold_list and hold_list.is_locked:
if role not in ("system_admin", "department_manager"):
raise PermissionDenied
Then each view calls it with the flags it needs, making the policy explicit and auditable.
Suggested fix order
- Add missing lock checks to
edit_item, update_pull_status, delete, and fulfil
- Add ownership checks to
lock, unlock, and fulfil (matching edit/delete)
- Gate pull status buttons behind
not hold_list.is_locked in the template
- Add Lock/Unlock/Delete/Fulfil buttons to
holdlist_detail.html with appropriate permission gating
- Consolidate permission logic into a single helper
- Add tests for each combination
Summary
An audit of the hold list permission logic reveals a patchwork of inconsistent checks across views, services, and templates. Ownership, lock status, and role gates are applied differently depending on which view handles the request, and several views that exist have no corresponding UI affordance.
Current state
Problems
1. Inconsistent ownership checks
holdlist_editandholdlist_deleterequire the user to be the creator (or admin/department_manager). Butholdlist_lock,holdlist_unlock,holdlist_fulfil, and all item-mutation views only check the role — any member can lock, unlock, fulfil, or mutate items on any hold list, even ones they didn't create.Decision needed: Should members be able to collaborate on any hold list (current behaviour for items), or should destructive/state-changing operations (lock, unlock, fulfil, delete) require ownership like edit does?
2. Missing lock checks
holdlist_edit_item— can edit item quantity/notes on a locked listholdlist_update_pull_status— can change pull status on a locked listholdlist_delete— can delete a locked hold listholdlist_fulfil— can bulk-checkout from a locked listadd_itemandremove_itemcorrectly delegate lock enforcement to the service layer, but the other views don't check at all — neither in the view nor in any service call.3. Missing template affordances
These views exist and are routed but have no corresponding button, link, or form in
holdlist_detail.html:Users can only reach these via direct URL, which means they're effectively invisible.
4. Pull status buttons shown on locked lists
The template renders "Mark Pulled" / "Mark Unavailable" / "Reset" buttons gated by
can_write_holdlistbut not byhold_list.is_locked. A member sees actionable buttons on a locked list, and the view will happily process the POST.5.
_require_holdlist_writeis too thinThe helper only checks role membership. Every view that needs ownership or lock enforcement does it inline (or doesn't). This leads to the inconsistencies above.
Proposal: Extend the helper or create a richer permission function, e.g.:
Then each view calls it with the flags it needs, making the policy explicit and auditable.
Suggested fix order
edit_item,update_pull_status,delete, andfulfillock,unlock, andfulfil(matchingedit/delete)not hold_list.is_lockedin the templateholdlist_detail.htmlwith appropriate permission gating