How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
Django · Python

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

Traceback (most recent call last): File "/venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File "/venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/venv/lib/python3.11/site-packages/django/contrib/auth/decorators.py", line 46, in _wrapper_view raise PermissionDenied File "/app/reports/views.py", line 15, in export_report if not request.user.has_perm('reports.can_export'): raise PermissionDenied('You do not have permission to perform this action.') django.core.exceptions.PermissionDenied: You do not have permission to perform this action.

Here's what each line means:

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.

Before (broken)
# 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...
After (fixed)
# 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:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. 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:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. 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)

  1. Run the full test suite locally to confirm permission checks pass.
  2. Open a pull request with the permission fix and migration.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. 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.