100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

Permissions and Groups

Django's permission system lets you gate actions per-model or per-object using auto-generated permissions, custom permissions, and reusable Groups that bundle permissions together.

Forms & AuthIntermediate10 min readJul 10, 2026
Analogies

Default Model Permissions

For every model, Django automatically creates four permissions during migration: add_<model>, change_<model>, delete_<model>, and view_<model>, stored in the auth_permission table and identified by a string like 'app_label.add_article'. You check them with user.has_perm('app_label.add_article'), which returns True if the permission is assigned either directly to the user or through any Group the user belongs to.

🏏

Cricket analogy: The four default permissions are like a ground's standard access tiers — pavilion entry, nets access, dressing-room access, and press-box access — automatically issued per venue without a groundskeeper hand-crafting each one.

Custom Permissions and Object-Level Checks

You can define additional permissions beyond the default four inside a model's Meta class using permissions = [('can_publish', 'Can publish articles')], which get created alongside the standard ones and checked the same way via has_perm(). Django's built-in ModelBackend only supports model-level permissions (not per-object), so for object-level checks — like 'can this user edit this specific article' — you typically need a third-party package like django-guardian or custom logic comparing article.author == request.user directly in the view.

🏏

Cricket analogy: A custom permission like can_publish is like a board issuing a special 'match referee' credential beyond the standard player/umpire tiers — a bespoke role added for a specific need not covered by default access levels.

Groups as Reusable Permission Bundles

A Group (django.contrib.auth.models.Group) is simply a named collection of permissions that you assign to a user via user.groups.add(group_instance), letting you manage access for entire teams — like 'Editors' or 'Moderators' — in one place instead of assigning permissions to hundreds of users individually. Since has_perm() checks both direct user permissions and group-derived permissions, updating a Group's permission set instantly changes access for every member without touching individual user records.

🏏

Cricket analogy: A Group is like a squad's 'central contract' tier that bundles match fees, insurance, and central pool bonuses into one package — update the tier once and every contracted player's benefits change together.

python
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.models import Group, Permission

# Assigning a user to a group (e.g. in a data migration or admin action)
editors_group, _ = Group.objects.get_or_create(name='Editors')
publish_perm = Permission.objects.get(codename='can_publish')
editors_group.permissions.add(publish_perm)
user.groups.add(editors_group)

@permission_required('articles.can_publish', raise_exception=True)
def publish_article(request, pk):
    article = get_object_or_404(Article, pk=pk)
    article.is_published = True
    article.save()
    return redirect('article-detail', pk=pk)

Superusers (is_superuser=True) automatically pass every has_perm() check regardless of assigned permissions or groups — useful for admin accounts but a reason to be careful about who gets superuser status.

Django's default ModelBackend permission checks are model-level, not object-level. has_perm('articles.change_article') tells you the user can change *some* article, not necessarily this specific one — always add explicit ownership checks (like article.author == request.user) for per-object protection unless you're using a package like django-guardian.

  • Django auto-creates add, change, delete, and view permissions for every model.
  • Custom permissions are declared in a model's Meta.permissions list.
  • user.has_perm('app_label.codename') checks both direct and group-derived permissions.
  • Groups bundle permissions so access can be managed for teams, not individual users.
  • Default permission checks are model-level; object-level checks require extra logic or django-guardian.
  • Superusers bypass all permission checks automatically.
  • @permission_required protects views the same way @login_required protects authentication.

Practice what you learned

Was this page helpful?

Topics covered

#Python#DjangoStudyNotes#WebDevelopment#PermissionsAndGroups#Permissions#Groups#Default#Model#StudyNotes#SkillVeris