TL;DR

Django provides a complete authentication and authorization sub-system out of the box. Use @login_required to restrict views to authenticated users and @permission_required to enforce granular access control based on custom permissions defined in your models.

Interesting!

Django automatically creates four default permissions for every model (add, change, delete, view) during migrations, but you can define custom permissions in your model’s Meta class to implement fine-grained authorization for any business logic you need.

The Authentication Foundation

Django’s authentication system lives in django.contrib.auth and works seamlessly with sessions via middleware. The @login_required decorator enforces authentication checks:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # Only authenticated users can access this view
    return render(request, 'template.html')

When an unauthenticated user tries to access a protected view, Django redirects them to the login page (defined by settings.LOGIN_URL) and stores the original URL in a “next” query parameter so they can be redirected back after login.

Custom Permissions in Models

You can define custom permissions in your model’s Meta class. These permissions use the format <app_label>.<permission_codename>:

from django.db import models

class Shape(models.Model):
    color = models.CharField(max_length=10)
    shape_type = models.CharField(max_length=10)

    class Meta:
        permissions = [
            ("view_shapes_canvas", "Can view the shapes canvas"),
            ("add_red_shape", "Can add red shapes"),
            ("add_green_shape", "Can add green shapes"),
            ("add_blue_shape", "Can add blue shapes"),
        ]

These custom permissions appear in the Django admin interface and can be assigned to individual users or groups.

The @permission_required Decorator

The @permission_required decorator enforces authorization checks before allowing view access:

from django.contrib.auth.decorators import permission_required

@login_required
@permission_required('shapes.view_shapes_canvas', raise_exception=True)
def canvas_view(request):
    # Only users with 'view_shapes_canvas' permission can access
    return render(request, 'canvas.html')

Setting raise_exception=True returns a 403 Forbidden response instead of redirecting to the login page. You can also pass multiple permissions as a list when a view requires several permissions.

Checking Permissions Programmatically

Use request.user.has_perm() to check permissions within your views:

@login_required
def add_shape(request):
    color = request.POST.get('color')

    # Check permission based on color
    if color == 'red' and not request.user.has_perm('shapes.add_red_shape'):
        messages.error(request, "You don't have permission to add red shapes!")
        return redirect('canvas')

    # Permission granted, create the shape
    Shape.objects.create(color=color, ...)

This pattern lets you implement authorization logic beyond simple built-in object-based checks.

Dynamic Permission-Based UI

Pass permission checks to your templates to enable or disable UI elements:

context = {
    'can_add_red': request.user.has_perm('shapes.add_red_shape'),
    'can_add_green': request.user.has_perm('shapes.add_green_shape'),
    'can_delete': request.user.has_perm('shapes.delete_shape'),
}
return render(request, 'canvas.html', context)

In your template:

<button {% if not can_add_red %}disabled{% endif %}>
    Add Red Shape
</button>

Automatic Permission Creation

Django automatically creates four permissions for each model during migrations: add_<modelname>, change_<modelname>, delete_<modelname>, and view_<modelname>. These are available immediately after running python manage.py migrate, assuming django.contrib.auth is in your INSTALLED_APPS.

Summary

This demonstrates how Django’s permission system enables fine-grained access control for any application logic, not just CRUD operations.

Django’s built-in authentication and permissions system provides everything you need to implement secure, granular authorization without reaching for third-party packages. The combination of decorators, programmatic checks, and custom permissions makes it straightforward to build applications with complex access control requirements.

Django’s decorator-based authentication builds on Python’s decorator patterns from functools ). The permission system integrates naturally with Python classes through model definitions. Understanding function annotations helps when working with Django’s type-aware tools and modern API frameworks.

Reference: Django Authentication System