Django from Basics to Production · APIs with Django REST Framework, and Production
ViewSets, Routers, Permissions and Pagination
Expose a model as a REST API with a ViewSet and router, restrict access per user, and paginate results.
What you'll build
A /notes/ API with a ModelViewSet and router: authenticated users see and edit only their own notes (filtered queryset plus an ownership permission), the owner is set from the request user, and lists are paginated.
The idea
- ViewSets bundle list, retrieve, create, update and delete; a router generates the URLs.
- Permissions (
IsAuthenticated, customhas_object_permission) decide access; the queryset decides what's visible. Filter the queryset by user so other users' objects are a 404, not a 403. - Set server-owned fields in
perform_create(serializer.save(owner=...)). - Pagination (page number, limit/offset, cursor) keeps responses bounded.
The code
import sys, types
import django
from django.conf import settings
sys.modules["shop"] = types.ModuleType("shop") # lets this single file act as the "shop" app
sys.modules["shop"].__file__ = __file__
settings.configure(
SECRET_KEY="dev-only", DEBUG=False, ALLOWED_HOSTS=["testserver"], ROOT_URLCONF=__name__,
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
INSTALLED_APPS=["django.contrib.contenttypes", "django.contrib.auth", "rest_framework", "shop"],
)
django.setup()
from django.contrib.auth.models import User
from django.core.management import call_command
from django.db import connection, models
from rest_framework import pagination, permissions, routers, serializers, viewsets
call_command("migrate", verbosity=0)
class Note(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.CharField(max_length=200)
class Meta: app_label = "shop"; ordering = ["id"]
with connection.schema_editor() as editor:
editor.create_model(Note)
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ["id", "text"]
class SmallPages(pagination.PageNumberPagination):
page_size = 2
class NoteViewSet(viewsets.ModelViewSet):
serializer_class = NoteSerializer
permission_classes = [permissions.IsAuthenticated]
pagination_class = SmallPages
def get_queryset(self):
return Note.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
router = routers.DefaultRouter()
router.register("notes", NoteViewSet, basename="note")
urlpatterns = router.urls
from rest_framework.test import APIClient
ana, ben = User.objects.create_user("ana"), User.objects.create_user("ben")
ben_note = Note.objects.create(owner=ben, text="ben's secret")
client = APIClient()
print("anonymous:", client.get("/notes/").status_code)
client.force_authenticate(ana)
for text in ["buy milk", "call mum", "book dentist"]:
client.post("/notes/", {"text": text}, format="json")
page1 = client.get("/notes/").json()
print("page 1:", page1["count"], "notes,", [n["text"] for n in page1["results"]], "next:", page1["next"])
print("ben's note as ana:", client.get(f"/notes/{ben_note.pk}/").status_code)
Output
anonymous: 403 page 1: 3 notes, ['buy milk', 'call mum'] next: http://testserver/notes/?page=2 ben's note as ana: 404
Run this on your computer — see “Get Started” for setup.
Common mistakes
queryset = Note.objects.all()with only a permission check on list: users see everyone's notes.- Accepting
ownerfrom the request body instead of setting it fromrequest.user. - No default pagination, so list endpoints return everything as data grows.
Key points
- ViewSets plus routers give you a full REST resource.
- Scope the queryset to the user; set owners server-side.
- Configure permissions and pagination as defaults in
REST_FRAMEWORKsettings.
Further reading: ViewSets · Permissions · Pagination
Practice questions
Why filter get_queryset by user rather than relying only on object permissions?
Answer
List endpoints don't check object permissions per row, so an unfiltered queryset leaks other users' data; filtering also makes others' objects a 404.
How should a viewset set the owner of a created object?
Answer
In perform_create by calling serializer.save(owner=self.request.user), never from client input.
Why configure pagination by default?
Answer
Without it list endpoints return every row, which grows slower and heavier as data increases.
Open in the app to mark lessons complete, take notes and track your progress.