Reference page for role-based access control (RBAC) in the dental app module (dental/). Use this when deciding what each role can see and do, when designing new views, or when reviewing access for DSPT / CQC compliance.
Source: analysis of
dental/templates/dental/base_dental.html,dental/views.py,dental/api/views.py,dental/consumers.py,core/middleware.py(RBACMiddleware),core/permissions.py,dsp_clinic/asgi.py,dsp_clinic/settings.py. Verified against the running app (2026-08-12, including the surgery-workspace WebSocket auth pass).
| Code | Role |
|---|---|
| DA | DENTAL_ADMIN — Dental Administrator |
| DPM | DENTAL_PRACTICE_MANAGER — Dental Practice Manager |
| DC | DENTAL_CLINICIAN — Dentist |
| DH | DENTAL_HYGIENIST — Hygienist / Therapist |
| DR | DENTAL_RECEPTIONIST — Dental Receptionist |
| AD | ADMIN (generic admin) |
| SU | Superuser |
PATIENT and PARTNER_* roles never see the dental navigation bar. The dental nav is rendered only for SU, AD, and the five DENTAL_* roles.
From dental/templates/dental/base_dental.html (✓ = menu item visible).
| Menu item | DA | DPM | DC | DH | DR | AD | SU |
|---|---|---|---|---|---|---|---|
| Dashboard | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Practices | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Appointments | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Staff | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Providers | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Triage | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
| Recalls | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Charting | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
| Periodontal | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
| Radiographs | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ |
| Financial | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
| Seats | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
| FP17 | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ |
| Scheduling | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Settings | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |
The Surgery Workspace has no nav entry of its own — it is reached from an
appointment (per-appointment URL). It is clinician-only in enforcement (below).
Sees: Dashboard, Practices, Appointments, Staff, Providers, Recalls, Scheduling
Does not see: Triage, Charting, Periodontal, Radiographs, Financial, Seats, FP17, Settings
base_dental.html wraps each menu item in role checks (see matrix above). UI-only.dental/views.py are still @login_required only, but the clinical-grade surfaces are now explicitly clinician-gated: radiograph_list / radiograph_upload / radiograph_detail / radiograph_image and surgery_workspace all use @dental_clinician_required (DA / DPM / DC / DH — same set as the middleware rule below). Everything else remains login-only.core/middleware.py::RBACMiddleware (active in settings) blocks by URL prefix. ROLE_REQUIREMENTS covers: /dental/practices/, /dental/appointments/, /dental/financial/, /dental/fp17/, /dental/recalls/, /dental/webhooks/, /dental/api/ and, since 2026-08-12, /dental/radiographs/ and /dental/surgery/ (both mapped to the clinician set DA / DPM / DC / DH). Every other dental URL still falls through to allow (any authenticated user). /media/ is no longer in EXEMPT_URLS (removed 2026-08-12) and nginx returns 404 for /media/dental/radiographs/.dental/api/views.py::BelongsToDentalSector permits all five DENTAL_* roles identically; reads scoped to the user's first active DentalProvider.practice (get_user_dental_practice); superuser sees all. No role-differentiated permissions beyond the radiograph hardening: DentalRadiographViewSet.perform_create forces practice to the caller's own practice, and its ingest action is clinician-only and rejects a foreign practice_id (400)./ws/surgery/stream/ (the surgery-workspace live AI pane) is authenticated at connect time (2026-08-12 DSPT Cat 3 fix): dsp_clinic/asgi.py wraps the WS route in channels.sessions.SessionMiddlewareStack (a stock AuthMiddlewareStack is deliberately NOT used — it resolves the user with no tenant context and would see client-tenant users as anonymous), and SurgeryStreamConsumer.connect() resolves the user from the session inside the tenant context. It closes with 4403 (→ HTTP 403 handshake) when the caller is anonymous, is not in the clinician set (DA/DPM/DC/DH), the ?tenant= param is missing/unknown/inactive, or the appointment belongs to another practice. _create_clinical_note re-verifies user + practice scope immediately before the signed-off-note write.@dental_clinician_required + middleware rule; PATIENT and DENTAL_RECEPTIONIST get 403 (previously 200) and can no longer POST to radiograph_upload.return 404 for /media/dental/radiographs/; /media/ removed from EXEMPT_URLS; uploads write PNGs to MinIO (processed/<patient>/<uuid>.png), never MEDIA_ROOT; viewing is only via the authenticated proxy /dental/radiographs/<id>/image/./dental/surgery/<pk>/ → 403 for receptionist/patient; /ws/surgery/stream/ rejects at connect (4403 → 403) for anonymous / non-clinician / cross-practice appointment / bad ?tenant=.radiograph_list scoped to the caller's practice; radiograph_detail/radiograph_image 404 for other-practice records; API radiograph create forced to the caller's practice; ingest rejects foreign practice_id; WS note commits re-check practice before write.Menu visible but blocked (403):
ADMIN is shown every menu item, but RBACMiddleware 403s ADMIN on Practices, Appointments, Recalls, Financial, FP17, /dental/api/ and, since the 2026-08-12 clinician gate, Radiographs and the Surgery Workspace (ADMIN is not in the DA/DPM/DC/DH set). ADMIN can actually open only the un-patterned paths (Dashboard, Staff, Providers, Triage, Charting, Periodontal, Seats, Scheduling, Settings).Reachable without a nav entry (any authenticated user — incl. PATIENT) — still open:
/dental/triage/, /dental/staff/, /dental/providers/, /dental/slots/, /dental/charts/, /dental/perio/, /dental/seats/, /dental/settings/zones/, /dental/settings/schedules/, /dental/dashboard/, /dental/recall/profile/…, /dental/mock/…/dental/dashboard/, /dental/charts/, /dental/perio/ and can POST to chart_create, perio_exam_create, seat_management, zone_create, schedule_create, provider_create, fp17_submit — those writes are still role-unchecked. (Radiographs and the surgery workspace are no longer in this class — fixed 2026-08-12.)Nav vs enforcement mismatch:
DH (DENTAL_HYGIENIST) has Radiographs hidden from the nav (and no nav entry for the surgery workspace) but IS in the clinician set — DH can open /dental/radiographs/ and /dental/surgery/ by URL. Allowed by policy, but nav and enforcement disagree; decide whether DH should see these in the nav.Functional bugs:
appointment_list returns DentalAppointment.objects.none() for every DENTAL_* role (only PATIENT/CLINICIAN/ADMIN/PRACTICE_MANAGER handled) → Appointments nav opens empty for all dental staff. The PATIENT branch is dead code (middleware 403s PATIENT).BelongsToDentalSector checks role only → a superuser whose role isn't a DENTAL_* value is denied /dental/api/ despite get_queryset granting "all"..none(); superuser gets all. DentalClinicalChart scopes via patient__dental_profiles__practice.DentalRadiographViewSet.perform_create forces the caller's practice and ingest is clinician-only + rejects foreign practice_id. Still open: other ModelViewSets (PeriodontalExamViewSet etc.) still accept an arbitrary practice FK from the request body on create → cross-practice write risk on charts/perio/transactions/FP17.radiograph_list is scoped to the caller's practice; radiograph_detail/radiograph_image 404 for other practices; the surgery workspace only serves the appointment's own practice and the WS commit is re-checked. Still open: staff_list, provider_list, financial_dashboard/practice_financials, fp17_claim_list, chart_list, perio_exam_list, triage_dashboard, recall_dashboard, seat_management, zone_*, schedule_*, slot_calendar iterate all practices — a DENTAL_PRACTICE_MANAGER sees every practice's finance, claims and clinical data via pages (API layer would restrict; pages do not)./media/ (nginx 404, /media/ out of EXEMPT_URLS); uploads go to MinIO; the authenticated proxy /dental/radiographs/<id>/image/ is the only way to view films.@dental_clinician_required and middleware rules. Still to do: the same treatment for /dental/charts/, /dental/perio/, /dental/triage/, /dental/staff/, /dental/providers/, /dental/seats/, /dental/settings/*, /dental/slots/.BelongsToDentalSector gives DENTAL_RECEPTIONIST identical CRUD to clinicians on clinical notes, charts, transactions and FP17 claims (incl. submit/return_to_draft/reconcile). Introduce role-specific permission classes; gate claim submit/reconcile to clinician+ / manager. (Radiograph create/ingest hardened 2026-08-12.)/ws/surgery/stream/ is clinician-only at connect, tenant-validated (?tenant= checked against active tenants), and note commits are practice-scoped — verified 2026-08-12 (anonymous/receptionist/patient → 403; cross-practice commit rejected).PeriodontalExamViewSet._resolve_practice hole and other API create paths.