Introduction
Django automatically creates four permissions (add, change, delete, view) for every model. Understanding how to check, assign, and customize these permissions is the foundation of access control in any Django application.
Key Concepts
Default Permissions: Django creates add_<model>, change_<model>, delete_<model>, and view_<model> for each model.
Permission String: Format is app_label.codename (e.g., blog.add_article).
has_perm(): Method on User to check a single permission.
permission_required: Decorator that restricts view access to users with specific permissions.
Meta.permissions: Define custom permissions beyond the default four.
Real World Context
In a multi-author blog, only staff members should be able to delete articles, while regular authors can only add and edit their own. Using Django's permission system with @permission_required('blog.delete_article') on the delete view enforces this rule without any custom logic in the view itself.
Deep Dive
Django automatically creates permissions for each model. Learn to use and customize them.
Default Model Permissions
For each model, Django creates:
add_<model>- Can add instanceschange_<model>- Can edit instancesdelete_<model>- Can delete instancesview_<model>- Can view instances
python# For model Article in app 'blog' # Permissions created: # - blog.add_article # - blog.change_article # - blog.delete_article # - blog.view_article
Checking Permissions
python# Check if user has permission user.has_perm('blog.add_article') # Single permission user.has_perm('blog.change_article') # Check multiple permissions (all required) user.has_perms(['blog.add_article', 'blog.change_article']) # Get all permissions user.get_all_permissions() # {'blog.add_article', 'blog.change_article', ...} # Get group permissions user.get_group_permissions()
In Views
pythonfrom django.contrib.auth.decorators import permission_required # Single permission @permission_required('blog.add_article') def create_article(request): pass # Multiple permissions (all required) @permission_required(['blog.add_article', 'blog.change_article']) def manage_article(request): pass # Raise 403 instead of redirecting to login @permission_required('blog.delete_article', raise_exception=True) def delete_article(request, pk): pass # Manual check in view def article_view(request): if not request.user.has_perm('blog.view_article'): return HttpResponseForbidden("Permission denied") # ...
In Templates
html{% if perms.blog.add_article %} <a href="{% url 'article_create' %}">Create Article</a> {% endif %} {% if perms.blog.change_article %} <a href="{% url 'article_edit' article.pk %}">Edit</a> {% endif %} {% if perms.blog %} <!-- User has ANY permission in blog app --> {% endif %}
Custom Model Permissions
pythonclass Article(models.Model): title = models.CharField(max_length=200) is_published = models.BooleanField(default=False) class Meta: permissions = [ ('publish_article', 'Can publish articles'), ('feature_article', 'Can feature articles on homepage'), ('archive_article', 'Can archive articles'), ] # After migration: # - blog.publish_article # - blog.feature_article # - blog.archive_article
Assigning Permissions
pythonfrom django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType # Get permission permission = Permission.objects.get( codename='publish_article', content_type__app_label='blog' ) # Assign to user user.user_permissions.add(permission) # Remove from user user.user_permissions.remove(permission) # Clear all permissions user.user_permissions.clear() # Check (must refetch user or clear permission cache) user = User.objects.get(pk=user.pk) # Refetch user.has_perm('blog.publish_article')
Creating Permissions Programmatically
pythonfrom django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType content_type = ContentType.objects.get_for_model(Article) permission, created = Permission.objects.get_or_create( codename='export_article', name='Can export articles', content_type=content_type )
Customizing Default Permissions
pythonclass Article(models.Model): title = models.CharField(max_length=200) class Meta: # Only create add and view permissions default_permissions = ('add', 'view') # Additional custom permissions permissions = [ ('publish_article', 'Can publish'), ]
Common Pitfalls
- Forgetting that superusers bypass all permission checks:
has_perm()always returnsTruefor superusers, even for permissions that do not exist. This can mask bugs in your permission logic during development. - Not refetching the user after assigning permissions: Django caches permissions on the user object. After
user.user_permissions.add(perm), you must refetch the user or deleteuser._perm_cacheto see the change. - Using
permission_requiredwithoutraise_exception=True: By default, the decorator redirects to the login page -- even for already-authenticated users who lack the permission. Setraise_exception=Trueto return a 403 instead.
Best Practices
- Use
permission_requireddecorator orPermissionRequiredMixin: Declarative access control is easier to audit than manualhas_perm()checks scattered in view logic. - Assign permissions via groups, not directly to users: This makes permission management scalable -- change the group's permissions once and all members are updated.
- Customize
default_permissionsfor models that do not need all four: Setdefault_permissions = ('view',)on read-only models to avoid creating unused permissions.
Summary
- Django creates add, change, delete, and view permissions for every model automatically.
- Check permissions with
user.has_perm('app.codename')or the@permission_requireddecorator. - Custom permissions are defined in
Meta.permissionsand created by migrations. - Assign permissions to groups, not individual users, for maintainability.
- Use the
permstemplate variable for conditional display in templates.
Code Examples
from django.contrib.auth.decorators import permission_required, login_required
@login_required
@permission_required('blog.add_article', raise_exception=True)
def create_article(request):
# Only users with 'blog.add_article' permission can access
pass