python
58 lines · 8 steps
How URL-namespaced API versioning works in Django
Two parallel URL groups route the same product endpoints to version-specific views and serializers so v1 and v2 can evolve independently.
Explained by
highlit
1from django.urls import path, include
2
3app_name = "api"
4
5v1_patterns = [
6 path("products/", ProductListV1.as_view(), name="product-list"),
7 path("products/<int:pk>/", ProductDetailV1.as_view(), name="product-detail"),
8]
9
10v2_patterns = [
11 path("products/", ProductListV2.as_view(), name="product-list"),
12 path("products/<int:pk>/", ProductDetailV2.as_view(), name="product-detail"),
13]
14
15urlpatterns = [
16 path("v1/", include((v1_patterns, "v1"), namespace="v1")),
17 path("v2/", include((v2_patterns, "v2"), namespace="v2")),
18]
19
20
21class ProductSerializerV1(serializers.ModelSerializer):
22 class Meta:
23 model = Product
24 fields = ["id", "name", "price"]
25
26
27class ProductSerializerV2(serializers.ModelSerializer):
28 price_cents = serializers.IntegerField(source="price_in_cents", read_only=True)
29 currency = serializers.CharField(read_only=True)
30
31 class Meta:
32 model = Product
33 fields = ["id", "name", "price_cents", "currency", "created_at"]
34
35
36class ProductListV1(generics.ListCreateAPIView):
37 queryset = Product.objects.active()
38 serializer_class = ProductSerializerV1
39
40
41class ProductDetailV1(generics.RetrieveUpdateDestroyAPIView):
42 queryset = Product.objects.active()
43 serializer_class = ProductSerializerV1
44
45
46class ProductListV2(generics.ListCreateAPIView):
47 queryset = Product.objects.active().select_related("category")
48 serializer_class = ProductSerializerV2
49
50
51class ProductDetailV2(generics.RetrieveUpdateDestroyAPIView):
52 queryset = Product.objects.active().select_related("category")
53 serializer_class = ProductSerializerV2
54
55 def get_serializer_context(self):
56 context = super().get_serializer_context()
57 context["request_version"] = "v2"
58 return context
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Isolating each API version behind its own URL prefix and namespace lets you change payloads without breaking existing clients.
- 2Swapping serializer_class per view is how the same model yields different response shapes across versions.
- 3Overriding get_serializer_context passes request-scoped data like the version down into serializer logic.
Related explainers
python
from functools import wraps from flask import Blueprint, abort, jsonify from flask_login import current_user, login_required
Building an admin-only decorator in Flask
decorators
authorization
access-control
Intermediate
7 steps
python
import asyncio from dataclasses import dataclass import aiohttp
Bounded-concurrency HTTP fetching with asyncio
async
concurrency
semaphore
Intermediate
8 steps
python
from pathlib import Path from typing import Iterator
Filtering files with pathlib.glob
generators
filesystem
filtering
Intermediate
6 steps
python
import heapq from collections import Counter from typing import Iterable, Hashable
Finding the top-N items in a stream
heaps
counting
generators
Intermediate
5 steps
python
from flask import Blueprint, jsonify from sqlalchemy import text from sqlalchemy.exc import SQLAlchemyError
Building a health check endpoint in Flask
health-check
blueprint
error-handling
Intermediate
6 steps
python
import pandas as pd import numpy as np
Cleaning a customer DataFrame with pandas
data-cleaning
regex
normalization
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-url-namespaced-api-versioning-works-in-django-explained-python-86a6/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.