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