~/TechPurAI
~/tutorials/django-rest-framework/permissions
intermediate·part 16 of 22·2 min read

Permissions: built-in classes and writing a custom one

Updated Aug 16, 2026Python · Django

Part 15 set up who a request claims to be. Nothing yet stops an anonymous request from creating, editing, or deleting any task — permissions are the layer that actually enforces that.

Requiring authentication globally

python
# taskapi/settings.py
REST_FRAMEWORK = {
    # ... existing settings ...
    "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
}

Every view now requires a valid token (or an active session) by default — an anonymous request gets 401 Unauthorized instead of ever reaching TaskViewSet at all. This is a reasonable global default for an API that's entirely private; a public-facing one usually wants something less blanket.

Read-only for anonymous, full access for authenticated

python
class TaskViewSet(ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

IsAuthenticatedOrReadOnly allows GET/HEAD/OPTIONS (the "safe" methods — the same ones part 15's GET example relied on) from anyone, while still requiring authentication for POST/PUT/PATCH/DELETE. Setting permission_classes directly on a view overrides DEFAULT_PERMISSION_CLASSES for that view specifically, the same override relationship pagination and filtering already had.

What's still missing: object-level access

Even with IsAuthenticated, any logged-in user can edit or delete any task — including tasks that belong to someone else. That's the exact gap the Django from scratch series closed with a get_queryset() override; DRF's version is a custom permission class.

python
# tasks/permissions.py
from rest_framework import permissions

class IsOwner(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.owner == request.user

has_object_permission runs once DRF already has a specific object to check — after the initial has_permission check (which BasePermission's default implementation always allows), and only for generic views and viewsets that call self.check_object_permissions(), which every DRF generic view and ModelViewSet action does automatically. permissions.SAFE_METHODS is ("GET", "HEAD", "OPTIONS") — allowing those unconditionally means anyone can read any task, but only the owner can modify or delete their own.

Applying it

python
class TaskViewSet(ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    permission_classes = [permissions.IsAuthenticated, IsOwner]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

Multiple permission classes in the list are ANDed together — IsAuthenticated blocks anonymous requests entirely, and IsOwner then restricts write access on top of that to whoever actually owns the specific task being modified. A request that fails has_object_permission gets 403 Forbidden — the "authenticated, but not allowed" code from part 6, distinct from the 401 an anonymous request gets from IsAuthenticated failing first.

Common mistake

Writing an IsOwner check and assuming it also filters the list endpoint. has_object_permission only runs for requests aimed at one specific object (retrieve, update, destroy) — a GET /tasks/ list is never checked against it at all. Restricting which tasks show up in a list — everyone's, versus just the logged-in user's — is a get_queryset() override, the same pattern from the Django from scratch series, layered on top of this permission rather than a replacement for it.

Next: throttling — a different kind of restriction, based on request rate rather than identity or ownership.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous15. Authentication: token-based API authnext →17. Throttling: rate limiting API requests