Fix PermissionDenied: You do not have permission to perform this action. in Django
This error means the authenticated user lacks the required permission for the requested action. Fix it by verifying the user has the correct permission assigned via Django's auth system, checking your permission_required decorator or has_perm calls, and ensuring the user's groups include the necessary permissions in the database.
Reading the Stack Trace
Here's what each line means:
- File "/venv/lib/python3.11/site-packages/django/contrib/auth/decorators.py", line 46, in _wrapper_view: Django's permission_required decorator checked the user's permissions and found them insufficient.
- File "/app/reports/views.py", line 15, in export_report: The view explicitly checks for the 'reports.can_export' permission using has_perm() and raises PermissionDenied if missing.
- raise PermissionDenied('You do not have permission to perform this action.'): Django converts this exception into a 403 Forbidden HTTP response.
Common Causes
1. Custom permission not created in migration
The model defines a custom permission in Meta.permissions, but the migration creating it was never run.
class Report(models.Model):
title = models.CharField(max_length=200)
class Meta:
permissions = [
('can_export', 'Can export reports'),
]
# Migration never created: python manage.py makemigrations not run
2. Permission not assigned to user or group
The permission exists in the database but was never assigned to the user directly or through a group.
# admin.py or management command
user = User.objects.get(username='analyst')
# Forgot to assign permission:
# user.user_permissions.add(Permission.objects.get(codename='can_export'))
3. Wrong permission codename string
The permission string passed to has_perm() uses the wrong format or a typo in the codename.
# Wrong format — should be 'app_label.codename'
if not request.user.has_perm('can_export'): # Missing app label
raise PermissionDenied
The Fix
Use the full 'app_label.codename' format for permission checks and the permission_required decorator for cleaner code. Ensure the permission was created by running migrations and is assigned to the user or their group.
# reports/views.py
from django.core.exceptions import PermissionDenied
def export_report(request):
if not request.user.has_perm('can_export'):
raise PermissionDenied('You do not have permission to perform this action.')
# export logic...
# reports/views.py
from django.contrib.auth.decorators import permission_required
@permission_required('reports.can_export', raise_exception=True)
def export_report(request):
# export logic...
# Assign permission via management command or admin:
# python manage.py shell
# from django.contrib.auth.models import Permission, User
# user = User.objects.get(username='analyst')
# perm = Permission.objects.get(codename='can_export')
# user.user_permissions.add(perm)
Testing the Fix
import pytest
from django.test import TestCase, Client
from django.contrib.auth.models import User, Permission
class TestExportPermission(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('analyst', password='testpass')
self.client.login(username='analyst', password='testpass')
def test_export_without_permission_returns_403(self):
response = self.client.get('/reports/export/')
assert response.status_code == 403
def test_export_with_permission_succeeds(self):
perm = Permission.objects.get(codename='can_export')
self.user.user_permissions.add(perm)
# Clear cached permissions
self.user = User.objects.get(pk=self.user.pk)
self.client.login(username='analyst', password='testpass')
response = self.client.get('/reports/export/')
assert response.status_code == 200
def test_unauthenticated_user_redirects(self):
self.client.logout()
response = self.client.get('/reports/export/')
assert response.status_code == 302
Run your tests:
pytest
Pushing Through CI/CD
git checkout -b fix/permission-denied-export,git add reports/views.py reports/models.py reports/migrations/,git commit -m "fix: use correct permission codename format and assign permissions",git push origin fix/permission-denied-export
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: test_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- run: pip install -r requirements.txt
- run: pytest --tb=short -q
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the root cause
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
pip install bugstack
Step 2: Initialize
import bugstack
bugstack.init(api_key=os.environ["BUGSTACK_API_KEY"])
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, verified fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Run the full test suite locally to confirm permission checks pass.
- Open a pull request with the permission fix and migration.
- Wait for CI checks to pass on the PR.
- Have a teammate review and approve the PR.
- Merge to main and run the migration in staging to create the permission.
Frequently Asked Questions
BugStack runs the fix through your existing test suite, generates tests for both authorized and unauthorized access paths, and validates that permissions are enforced correctly before marking it safe to deploy.
BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.
Yes. Django caches permissions on the user object. After adding permissions programmatically, re-fetch the user from the database or the new permissions will not take effect until the next request.
Yes. Groups are the recommended approach for managing permissions at scale. Create a group with the required permissions and add users to it instead of assigning permissions individually.