Troubleshooting Access
Use this page when a user can see a screen, menu, action or report they should not see, or cannot see something they should. Diagnose the source before changing roles or privileges.
First command
python manage.py rbac_explain --user <email> --target <app>
The report shows:
is_superuser,is_staffand active status;- active and inactive roles;
- every linked privilege;
- fallback and legacy links;
- role overrides;
- effective models, menus, actions and outputs with the privilege that granted each one.
Diagnostic order
Use this order so a stale catalogue or a broken template is not mistaken for a role problem:
python manage.py checkpython manage.py rbac_syncpython manage.py rbac_bootstrappython manage.py rbac_audit --strict --list --list-role-riskspython manage.py rbac_explain --user <email> --target <app>- Reload the page in a fresh request (the permission snapshot is request-scoped).
If rbac_sync fails, fix that first. The role data is not updated until the sync transaction commits.
Cause matrix
| Symptom | Check | Fix |
|---|---|---|
| User sees everything | is_superuser=True |
This is intended for unassigned elements. Do not use superuser as a normal staff account. |
| User sees an app they were not granted | User has a second role, or NORMAL_USER has the app's privilege |
Remove the extra role or privilege; use a task privilege. rbac_sync removes .admin and legacy system.administrator links from NORMAL_USER. |
| User sees a newly added screen | Fallback privilege linked to a role, a non-baseline privilege linked to NORMAL_USER, or a task target still in a broad .use bundle |
Run rbac_sync; it deactivates task targets in .use bundles. rbac_audit --list-role-risks lists remaining risks. |
| User sees a screen after a sync | The target was added to an existing privilege | Check the privilege's element list; normal syncs are not supposed to widen an existing privilege. |
User sees HR through /admin/ |
is_staff=True plus Django model permissions |
Unregister the business model or use RbacAdminModelAdminMixin. |
| User can open an HR report or dashboard without an HR role | function.reports.use still contains an HR output from an older sync |
Run rbac_sync; it deactivates task targets inside broad .use bundles. Check rbac_audit --list-role-risks. |
| Menu is granted but access fails | The menu gate requires a model/action/output permission | Grant the gate operation, or attach the menu to the task that includes it. |
| A granted module menu causes an error | Self-gating recursion | Run rbac_sync after the self-gate fix; confirm gate_target_code != code. |
| User can open a screen but not a related lookup | The related model lacks view |
Add view for that related model to the task. |
| Report is hidden | Module menu or output target not granted | Add the module menu and the output code to the task. |
| Access changes are not taking effect | Request-scoped snapshot is cached on the request | Reload the page; a new request rebuilds the snapshot. |
NORMAL_USER gets 403 on /dashboards/enezacommon/ |
The Common module menu is not granted, or the module menu target does not exist | Run rbac_sync; confirm EnezacommonConfig.app_entry_menu is set and menu.enezacommon.__module__ exists. |
| A 403 response is plain text or Django's bare page | Django cannot find 403.html, or a custom handler bypasses it |
Confirm enezacommon is installed, APP_DIRS=True, and enezacommon/templates/403.html exists. Restart the server after adding it in production. |
| The 403 page loads without its designed styling | A project-level 403.html is shadowing the shipped template, or the server cached the old template |
Remove the shadowing template or restart/clear the template cache. The shipped page uses inline CSS and needs no static files. |
| Dashboard is visible but a tab or widget returns 403 | The dashboard access_code or a widget output is missing |
Grant the missing output or dashboard access code; check rbac_explain. For the seeded HR roles, run python manage.py seed_hr_roles. |
HR manager dashboard shows report_forbidden on every widget |
The HR_MANAGER role has the HR menus and models but no HR report outputs |
Run python manage.py seed_hr_roles; it adds hr.reports.manager.view. A custom role can instead receive task.humanresource.reports.view. |
Common dashboard: /dashboards/enezacommon/
NORMAL_USER should reach the Common module through the baseline
function.common.use privilege. Check these in order:
EnezacommonConfighasmenu_verbose_name = "Common"andapp_entry_menu = "enezacommon:defaultDashboard".python manage.py rbac_syncreports the Common menu target as protected.python manage.py rbac_explain --user <email> --target enezacommonshowsmenu.enezacommon.__module__withfunction.common.useas its source.- The
my_workspacedashboard is active, published and has a published version. An unpublished dashboard is hidden even when the menu is granted. - The dashboard has no
access_codethat the user lacks. - If the page renders but widgets show permission errors, the widget report
outputs are missing.
function.reports.usegrants normal report outputs; task-scoped outputs must be granted by their task. The seededHR_MANAGERandHR_ASSISTANTroles receive their reports throughhr.reports.manager.viewandhr.reports.assistant.view; rerunpython manage.py seed_hr_rolesafter report changes.
If rbac_sync was run before app_entry_menu was added, the module target does
not exist yet. The sync is safe to rerun; it creates the target, the
function.common.use privilege and the NORMAL_USER link.
403 page troubleshooting
The project ships enezacommon/templates/403.html. Django's default 403
handler looks for a template named 403.html in the app template directories.
- Confirm
APP_DIRS=Trueis configured. - Confirm
enezacommonis inINSTALLED_APPS. - Confirm no project-level
403.htmlor customhandler403shadows the template. - The shipped page uses inline CSS and no static files. If it looks unstyled, check for a shadowing template or a cached old template, then restart the server.
- The theme mirrors the Alpine
_x_themevalue:darkrenders dark, whilelightandsystemrender light (the current app layout does not resolvesystemto dark). No stored value falls back to the app default, dark. - A signed-out user is redirected to login rather than shown 403. The 403 page is for an authenticated account that lacks the required task.
- After adding or changing the template, restart the server or clear the template cache in production.
The page intentionally shows only the signed-in user and the 403 reference. It does not expose the required privilege code or hidden route names.
rbac_sync failures
| Error | Likely cause | Fix |
|---|---|---|
NameError or ImportError after an upgrade |
The running shell or server has stale code | Restart it and run python manage.py check. |
OperationalError: no such table |
Migrations have not run in this environment | Run python manage.py migrate. |
ValidationError about a privilege code |
A custom privilege uses an invalid task. code or a non-system function. code |
Use task.<app>.<object>.<operation> or rename the custom code. |
ImproperlyConfigured about app_labels |
A user_facing=True spec uses app_labels without allow_app_wide_use=True |
Replace the app-wide selector with explicit labels/menus/actions. |
| Sync completes but the old access remains | The source is a custom privilege or a role override, not the fallback | Run rbac_explain and remove the custom link or override. |
rbac_sync is atomic. If it fails, none of its changes commit; fix the error and
rerun it. Warnings W003 and W004 seen before the sync are expected when an
older database still has fallback or admin links. They should disappear after a
successful sync.
Read-only checks
Run these in python manage.py shell only after taking a backup and only when the user asks for diagnosis.
from django.contrib.auth import get_user_model
from enezarbac.models import RolePrivilege, UserRole
user = get_user_model().objects.get(email="user@example.com")
print(user.is_superuser, user.is_active)
print(list(UserRole.objects.filter(user=user).values_list("role__code", flat=True)))
print(list(
RolePrivilege.objects.filter(role__user_roles__user=user)
.values_list("role__code", "privilege__code")
))
Check the fallback and legacy links:
from enezarbac.models import RolePrivilege
print(list(
RolePrivilege.objects.filter(
privilege__code__in=[
"function.unclassified.admin",
"system.administrator",
]
).values_list("role__code", "privilege__code")
))
Check whether HR is still unclassified:
python manage.py rbac_audit --list-unclassified --list-role-risks
What not to do
- Do not add the user to
SYSTEM_ADMINISTRATORto "fix" a missing screen. - Do not link
function.unclassified.adminto a role. - Do not mark the endpoint
@rbac_public. - Do not disable
RBAC_SUPERUSER_BYPASSto solve a role problem. - Do not run
rbac_bootstrap --reset-functionalto hide a manual grant; reset restores seeded definitions.
When the audit is clean
Enable strict denial only after the audit is clean:
RBAC_DENY_UNREGISTERED_ENDPOINTS = True
Then run:
python manage.py check
python manage.py rbac_audit --strict
The user is solely responsible for testing and validation.