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

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Isolating each API version behind its own URL prefix and namespace lets you change payloads without breaking existing clients.
  2. 2Swapping serializer_class per view is how the same model yields different response shapes across versions.
  3. 3Overriding get_serializer_context passes request-scoped data like the version down into serializer logic.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How URL-namespaced API versioning works in Django — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code