The system uses a permission matrix approach to manage user access across all major applications. Instead of Django Group-based permissions, the AppAccessPermission model provides a centralized, granular access control system.
This design provides: - Unified permission management across all modules - Four-level access control (None, Read, Write, Delete) - Clear permission resolution rules - Efficient auditing and access reporting
The system uses the AppAccessPermission model to grant users access to applications:
from core.access_control import AppAccessPermission
# Grant user write access to questions app
AppAccessPermission.objects.create(
user=user,
app_code='questions',
access_level='write'
)
Four hierarchical access levels control user capabilities:
Level Hierarchy: None < Read < Write < Delete
When checking access, the system evaluates:
AppAccessPermission entries applyUse PermissionCheck to verify access:
from core.access_control import PermissionCheck
# Check if user can read questions
check = PermissionCheck(user=request.user, app_code='questions', required_level='read')
if check.allows():
# User has read or higher access
display_questions()
# Check if user can write (edit) questions
check = PermissionCheck(user=request.user, app_code='questions', required_level='write')
if check.allows():
# User can view and edit
show_edit_form()
# Check if user can delete (full admin)
check = PermissionCheck(user=request.user, app_code='questions', required_level='delete')
if check.allows():
# User has full administrative access
show_delete_button()
The permission matrix applies to these applications:
| App Code | Purpose |
|---|---|
| questions | Question management and administration |
| client_portal | Client portal access |
| reports | Report generation and viewing |
| assignments | Person-project assignments |
| companies | Company and person management |
| projects | Project management |
from django.contrib.auth.models import User
from core.access_control import AppAccessPermission
# Get the user
user = User.objects.get(username='john_smith')
# Grant read access to questions
AppAccessPermission.objects.create(
user=user,
app_code='questions',
access_level='read'
)
# Grant write access to client_portal
AppAccessPermission.objects.create(
user=user,
app_code='client_portal',
access_level='write'
)
# Grant delete access to projects
AppAccessPermission.objects.create(
user=user,
app_code='projects',
access_level='delete'
)
# View all permissions for user
permissions = AppAccessPermission.objects.filter(user=user)
for perm in permissions:
print(f"{perm.user}: {perm.app_code} -> {perm.access_level}")
python manage.py debug_user john_smith
Output shows all app access levels for the user.
User sees: - Application not available in menu - 404 error if accessing directly - No data visible - No action buttons
Typical: Users with no role in a particular module
User sees: - Application in menu - Read-only list views of all records - View-only detail pages with read-only form fields - No "Add" button - No "Edit" or "Save" buttons - No "Delete" button - Filtering and searching enabled
User can do: - View projects/questions/companies/etc. - Search and filter data - View record details - Export data (if applicable)
Typical: Reporting staff, auditors, view-only managers
User sees: - Application in menu (full access) - Editable list views with all CRUD buttons - Edit detail pages with editable form fields - "Add" button to create new records - "Save" button to update records - "Cancel" button to discard changes - Filtering, searching, and sorting
User can do: - View all records - Create new records - Edit existing records - Search and filter - Bulk operations (if available) - Export data
User cannot do: - Delete records - Manage other users' access
Typical: Data managers, project leads, content editors
User sees: - Application in menu (unrestricted) - All management capabilities - "Add" button to create records - "Edit" capabilities on all fields - "Save" button - "Delete" button on each record - Bulk delete operations (if available) - User and permission management (if applicable)
User can do: - All Write operations - Delete records permanently - Manage user access (for their application) - System-level configuration - Archive/restore operations
Typical: System administrators, application owners, compliance staff
# View permissions for a specific user
python manage.py debug_user username
# Example output:
# User: john_smith (id=5)
# Email: john.smith@company.com
# Superuser: No
#
# App Access Levels:
# questions: write
# client_portal: read
# reports: read
# projects: none
Django Admin provides a matrix view:
from core.access_control import AppAccessPermission
from django.contrib.auth.models import User
# Generate report
for user in User.objects.filter(is_active=True):
perms = AppAccessPermission.objects.filter(user=user)
print(f"{user.username}: {dict((p.app_code, p.access_level) for p in perms)}")
from core.access_control import AppAccessPermission
# Change user's questions access to write
perm = AppAccessPermission.objects.get(user_id=5, app_code='questions')
perm.access_level = 'write'
perm.save()
# Or delete if no longer needed
perm.delete()
from core.access_control import AppAccessPermission
from django.contrib.auth.models import User, Group
# Grant all users in a department write access to projects
users = User.objects.filter(groups__name='Consultants')
for user in users:
AppAccessPermission.objects.get_or_create(
user=user,
app_code='projects',
defaults={'access_level': 'write'}
)
# Completely remove user access to an application
AppAccessPermission.objects.filter(
user_id=5,
app_code='questions'
).delete()
A new consultant joins the organization and needs project management access:
user = User.objects.create_user('jane_doe', email='jane@company.com', password='secure123')
AppAccessPermission.objects.create(user=user, app_code='projects', access_level='write')
AppAccessPermission.objects.create(user=user, app_code='questions', access_level='write')
AppAccessPermission.objects.create(user=user, app_code='reports', access_level='read')
Result: - Jane can create and edit projects - Jane can manage questions - Jane can view reports but not modify them
An auditor needs to view all data but make no changes:
user = User.objects.create_user('audit_team', email='audit@company.com', password='secure123')
for app in ['questions', 'projects', 'reports', 'assignments', 'companies']:
AppAccessPermission.objects.create(user=user, app_code=app, access_level='read')
Result: - Auditor can view all data - Auditor cannot create, edit, or delete anything - Audit trail shows all viewing activity
A department manager runs the client portal and manages persons:
user = User.objects.create_user('dept_manager', email='manager@company.com')
AppAccessPermission.objects.create(user=user, app_code='client_portal', access_level='write')
AppAccessPermission.objects.create(user=user, app_code='companies', access_level='write')
AppAccessPermission.objects.create(user=user, app_code='reports', access_level='read')
Result: - Manager can administer client portal - Manager can add/edit company and person records - Manager can view reports - Manager has no access to questions or projects
The system admin has full access everywhere:
user = User.objects.create_user('sysadmin', email='admin@company.com', is_superuser=True)
# Superuser: no permissions needed, has everything
# (Optional: create explicit entries for audit purposes)
Result: - Admin sees all applications - Admin can perform all operations - Admin bypasses permission checks
Grant users only the minimum access they need:
# ✅ Good: Specific user, specific app, minimal level
AppAccessPermission.objects.create(user=user, app_code='reports', access_level='read')
# ❌ Bad: Making everyone a superuser
user.is_superuser = True # Don't do this
Match access level to job responsibility:
| Role | Recommended Level | Reason |
|---|---|---|
| Viewer/Reporter | Read | Only view data |
| Data Entry/Editor | Write | Create and update |
| Manager/Admin | Delete | Full control |
| Superuser | n/a | Has everything |
Review permissions quarterly:
# List all access permissions
python manage.py shell
from core.access_control import AppAccessPermission
AppAccessPermission.objects.all().count() # How many entries?
# Find overly permissive entries
from core.access_control import AppAccessPermission
AppAccessPermission.objects.filter(access_level='delete').count() # Who has admin?
When modifying access: - Record the business reason - Note who made the change - Include date and authorization
When a user leaves, deactivate rather than delete:
# ❌ Never delete
# user.delete()
# ✅ Deactivate to preserve audit trail
user.is_active = False
user.save()
Symptoms: User sees "No permission" or application doesn't appear in menu
Check:
python manage.py debug_user username
Verify: - Is AppAccessPermission entry created? - Is access_level set to 'read' or higher? - Is user account active? - Is user a superuser?
Fix:
from core.access_control import AppAccessPermission
user = User.objects.get(username='username')
AppAccessPermission.objects.create(
user=user,
app_code='projects',
access_level='read'
)
Symptoms: User sees read-only forms despite expecting edit access
Check:
python manage.py debug_user username
Verify: - Does AppAccessPermission have access_level='write' or 'delete'?
Fix:
perm = AppAccessPermission.objects.get(user_id=5, app_code='projects')
perm.access_level = 'write' # Upgrade from 'read' to 'write'
perm.save()
Symptoms: is_superuser=True but permission management panel not visible
Note: Superusers have all permissions but may not have explicit AppAccessPermission entries. This is normal. Explicit entries are for regular users.
Symptoms: User updated to write access but still sees read-only
Likely Cause: Browser session cached old permissions
Fix:
- User logs out and logs back in
- Clear browser cache
- Developer: Django permission cache can be cleared with user.refresh_from_db()
| Aspect | Details |
|---|---|
| Model | AppAccessPermission |
| Location | Django Admin → Core → App Access Permissions |
| Access Check | PermissionCheck(user, app_code, required_level) |
| Levels | None, Read, Write, Delete |
| Resolution | Superuser > Matrix > None |
| Debug | python manage.py debug_user <username> |
| Audit Trail | Created/modified timestamps on entries |
Last Updated: January 2026
Version: 3.0 (Permission Matrix)
Previous: 2.0 (Django Groups - Deprecated)
Related: Core Access Control Module
Each module has one consolidated view that adapts based on permissions:
/projects/projects/manage//projects/categories/manage//companies/companies/manage//companies/persons/manage//questions/manage//questions/types/manage//questions/roles/manage//questions/categories/manage/Access: All authenticated users
Behavior: Interface adapts based on user's group membership
The system uses pre-configured groups for each module with two access levels:
Admin Groups (Full Access): - Admin Projects - Manage projects - Admin Project Categories - Manage project-category mappings - Admin Companies - Manage companies - Admin Persons - Manage persons - Admin Questions - Manage questions - Admin Question Types - Manage question types - Admin Question Roles - Manage question roles - Admin Question Categories - Manage question categories
View Groups (Read-Only): - View Projects - View projects only - View Project Categories - View mappings only - View Companies - View companies only - View Persons - View persons only - View Questions - View questions only - View Question Types - View types only - View Question Roles - View roles only - View Question Categories - View categories only
Purpose: For users who need to manage data
Permissions:
- view_* - View records
- add_* - Create new records
- change_* - Edit existing records
- delete_* - Delete records
projects.view_projectprojects.add_projectprojects.change_projectprojects.delete_projectprojects.view_projectcategoriesprojects.add_projectcategoriesprojects.change_projectcategoriesprojects.delete_projectcategoriescompanies.view_companiescompanies.add_companiescompanies.change_companiescompanies.delete_companiescompanies.view_personscompanies.add_personscompanies.change_personscompanies.delete_personsquestions.view_questionsquestions.add_questionsquestions.change_questionsquestions.delete_questionsquestions.view_questiontypesquestions.add_questiontypesquestions.change_questiontypesquestions.delete_questiontypesquestions.view_questionrolesquestions.add_questionrolesquestions.change_questionrolesquestions.delete_questionrolesquestions.view_questioncategoriesquestions.add_questioncategoriesquestions.change_questioncategoriesquestions.delete_questioncategoriesWhat Admin Users See: - ✅ "+ Add" button - Create new records - ✅ Clickable IDs - Open record details for editing - ✅ Editable form fields - All fields can be modified - ✅ "Save" button - Commit changes to database - ✅ "Cancel" button - Close without saving
Typical Admin Users: - Department administrators - Data entry staff - Project managers - System administrators
Purpose: For users who need to view information but not modify it
Permissions:
- view_* - View records only (no add, change, or delete)
projects.view_projectprojects.view_projectcategoriescompanies.view_companiescompanies.view_personsquestions.view_questionsquestions.view_questiontypesquestions.view_questionrolesquestions.view_questioncategoriesWhat View-Only Users See: - ❌ No "+ Add" button - ✅ Clickable IDs - Open record details for viewing - 🔒 Read-only form fields - Fields are disabled/readonly - ❌ No "Save" button - ✅ "Close" button - Close the view dialog
Typical View-Only Users: - External stakeholders - Auditors - Reporting staff - Client users - Read-only administrators
The template uses Django's permission system:
{% if can_add %}
<button id="btn_add">+ Add Project</button>
{% endif %}
<input type="text" id="edit_project_name" {% if not can_change %}readonly{% endif %} />
{% if can_change %}
<button id="btn_save">Save</button>
{% endif %}
const canAdd = {{ can_add|lower }}; // Boolean from backend
const canChange = {{ can_change|lower }}; // Boolean from backend
// All users can click IDs to view
const idCell = `<a href="#" data-id="${p.id}">${p.id}</a>`;
// Save button only wired if can_change is true
if(btnSave && canChange){
btnSave.addEventListener('click', saveProject);
}
Run the management commands to create all permission groups:
# Projects
python manage.py create_project_groups
# Project Categories
python manage.py create_project_category_groups
# Companies
python manage.py create_company_groups
# Persons
python manage.py create_person_groups
# Questions
python manage.py create_question_groups
# Question Types
python manage.py create_question_type_groups
# Question Roles
python manage.py create_question_role_groups
# Question Categories
python manage.py create_question_category_groups
Output Example:
Creating Admin Projects group...
Creating View Projects group...
Successfully created both groups and assigned permissions.
from django.contrib.auth.models import User, Group
# Get the user
user = User.objects.get(username='johndoe')
# Assign to admin groups (full access)
admin_projects = Group.objects.get(name='Admin Projects')
admin_companies = Group.objects.get(name='Admin Companies')
user.groups.add(admin_projects, admin_companies)
# OR assign to view groups (read-only)
view_projects = Group.objects.get(name='View Projects')
view_companies = Group.objects.get(name='View Companies')
user.groups.add(view_projects, view_companies)
# Verify
print(user.groups.all())
This same pattern has been applied across all major modules:
/projects/projects/manage//projects/categories/manage//companies/companies/manage//companies/persons/manage//questions/manage//questions/types/manage//questions/roles/manage//questions/categories/manage/All modules follow the same pattern: - Single unified page per module - Permission-based UI adaptation - Admin users: full CRUD capabilities - View users: read-only access - All IDs clickable for both permission levels - Active filters default to checked (where applicable)
A: Yes! Users can belong to multiple groups. Their effective permissions are the union of all groups.
Example: - User in "View Projects" + "Admin Companies" - Can view projects (read-only) - Can fully manage companies
A: They lose editing capabilities immediately after logout/login. If they're not in any group, they can still view the page but with no Add button and read-only forms.
A: Yes, but it's not recommended. Use groups for consistency. Individual permissions can be assigned via Django Admin under "User permissions" but this becomes hard to manage at scale.
A: No. Superusers have all permissions automatically, regardless of group membership.
A: Create a test user, assign to "View Projects" group, logout from admin account, and login as the test user. Or use Django's permission testing in a separate browser session.
Symptoms: User expects to add records but no "+ Add" button appears
Check:
1. Is user in the correct Admin group?
- Projects: Admin Projects
- Categories: Admin Project Categories
- Companies: Admin Companies
- Persons: Admin Persons
2. Does user have add_* permission?
3. Has user logged out and back in after permission change?
Fix:
# Verify user permissions and groups
user = User.objects.get(username='username')
# Check specific permission
print(user.has_perm('projects.add_project')) # Should be True
print(user.has_perm('companies.add_companies')) # Should be True
# Check groups
print(user.groups.all()) # Should include relevant Admin group
Symptoms: User can type in fields but clicking Save does nothing
Check:
1. User has change_* permission?
2. JavaScript console for errors?
3. API endpoint returning 403 Forbidden?
4. Network tab showing failed POST request?
Fix: - Ensure user is in the correct Admin group - Check API permissions in ViewSet - Verify CSRF token is present - Check browser console for JavaScript errors
Debug Commands:
user = User.objects.get(username='username')
print(user.has_perm('projects.change_project')) # Should be True
print(user.groups.filter(name='Admin Projects').exists()) # Should be True
Symptoms: User in Admin group sees read-only fields
Check:
1. Template using {% if can_change %} correctly?
2. View passing can_change in context?
3. Permission cached from old session?
4. Typo in group name assignment?
Fix:
# In views.py - verify context
context = {
'can_add': request.user.has_perm('projects.add_project'),
'can_change': request.user.has_perm('projects.change_project'),
}
# Check user session
user.refresh_from_db()
user.groups.all()
Ask user to: - Log out completely - Clear browser cache - Log back in - Try again
Symptoms: user.has_perm() returns True but UI still read-only
Check: 1. User logged in as correct account? 2. Session cached old permissions? 3. Template receiving correct context variables?
Fix:
- Add print(can_add, can_change) to view to verify values
- Check template debug toolbar if enabled
- Have user clear cookies and re-login
- Restart Django development server
Symptoms: Group exists but members can't do anything
Check: 1. Management command ran successfully? 2. Permissions assigned to group? 3. ContentType exists for model?
Fix:
from django.contrib.auth.models import Group
group = Group.objects.get(name='Admin Projects')
print(group.permissions.all()) # Should show 4 permissions
# Re-run management command if empty
python manage.py create_project_groups
Symptoms: User in View group can modify data
Possible Causes: 1. User in BOTH Admin and View groups (Admin wins) 2. User is superuser (bypasses all permission checks) 3. API endpoint doesn't enforce permissions
Fix:
user = User.objects.get(username='username')
# Check all groups
print(user.groups.all()) # Should only be View groups
# Check superuser status
print(user.is_superuser) # Should be False
# Remove from admin groups if needed
admin_group = Group.objects.get(name='Admin Projects')
user.groups.remove(admin_group)
Symptoms: /projects/projects/ shows 404 instead of redirecting
Check:
1. URL patterns in pulse_config/urls.py
2. Lambda redirect function present?
3. URLs reloaded after code change?
Fix: - Restart Django development server - Check urls.py for redirect:
path('projects/projects/', lambda request: redirect('/projects/projects/manage/')),
Symptoms: Active checkbox doesn't filter results
Check:
1. Filter checkbox wired in JavaScript?
2. API call includes is_active parameter?
3. ViewSet has is_active in filterset_fields?
Fix in template:
const filterActive = document.getElementById('filter_active');
filterActive.addEventListener('change', loadData);
// In loadData() function
const url = `/api/module/?is_active=${filterActive.checked}`;
Symptoms: Projects works but Companies doesn't
Likely Cause: Inconsistent implementation
Check: 1. All four management commands run? 2. Template patterns consistent across modules? 3. API ViewSets have same permission classes?
Verify All Groups:
python manage.py shell
from django.contrib.auth.models import Group
Group.objects.filter(name__startswith='Admin').values_list('name', flat=True)
# Should show: Admin Projects, Admin Project Categories, Admin Companies, Admin Persons
Group.objects.filter(name__startswith='View').values_list('name', flat=True)
# Should show: View Projects, View Project Categories, View Companies, View Persons
If you previously had separate /projects/projects/ (view) and /projects/projects/manage/ (edit) pages:
/projects/projects/) now redirects to /projects/projects/manage/The redirect is automatic. Old bookmarks will work.
| Aspect | Admin Groups | View Groups |
|---|---|---|
| View Records | ✅ Yes | ✅ Yes |
| Add New Records | ✅ Yes | ❌ No |
| Edit Records | ✅ Yes | ❌ No (read-only) |
| Delete Records | ✅ Yes | ❌ No |
| "+ Add" Button | ✅ Visible | ❌ Hidden |
| Form Fields | ✅ Editable | 🔒 Read-only/Disabled |
| "Save" Button | ✅ Visible | ❌ Hidden |
| "Cancel/Close" Text | "Cancel" | "Close" |
| Dialog Title | "Edit..." / "Add..." | "View..." |
| Clickable IDs | ✅ Yes | ✅ Yes |
| Module | Admin Group | View Group |
|---|---|---|
| Projects | Admin Projects | View Projects |
| Project Categories | Admin Project Categories | View Project Categories |
| Companies | Admin Companies | View Companies |
| Persons | Admin Persons | View Persons |
| Questions | Admin Questions | View Questions |
| Question Types | Admin Question Types | View Question Types |
| Question Roles | Admin Question Roles | View Question Roles |
| Question Categories | Admin Question Categories | View Question Categories |
| Module | URL | Old URLs (Redirected) |
|---|---|---|
| Projects | /projects/projects/manage/ |
/projects/projects/ |
| Project Categories | /projects/categories/manage/ |
/projects/categories/ |
| Companies | /companies/companies/manage/ |
/companies/companies/ |
| Persons | /companies/persons/manage/ |
/companies/persons/ |
| Questions | /questions/manage/ |
/questions/ |
| Question Types | /questions/types/manage/ |
/questions/types/ |
| Question Roles | /questions/roles/manage/ |
/questions/roles/ |
| Question Categories | /questions/categories/manage/ |
/questions/categories/ |
Last Updated: January 2025
Version: 2.0
Related: User Permissions Management Guide