Versioning views
View actions / methods
Let's say we want to add a new action to the Dogs viewset -- a view for individual dogs. Paste the following code into your doggies/views.py:
from drf_versioning.decorators import versioned_view
from rest_framework import viewsets, mixins
from doggies.models import Dog
from doggies.serializers import DogSerializer
from versioning import versions
class DoggieViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin):
serializer_class = DogSerializer
queryset = Dog.objects.all()
@versioned_view(introduced_in=versions.VERSION_2_0_0)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
Here we have
- Added the RetrieveModelMixin to the viewset, which allows us to see the detail view at
/doggies/<dog-id>/ - Overridden the
retrievemethod and applied theversioned_viewdecorator, specifying the version from which this view should become available.
Version 2.0.0 doesn't exist yet, so let's create it. Add this to your versioning/versions.py:
VERSION_2_0_0 = Version(
"2.0.0",
notes=["Added doggie detail view"],
)
and add it to the list of supported versions in versioning/version_list.py:
VERSIONS = [
versions.VERSION_2_0_0,
versions.VERSION_1_0_0,
]
Now if we ping the /version/ endpoint, we should see the new Version. Note that in addition to the notes which we added to the Version instance by hand, the versioned_view decorator has also informed the Version instance about the new view, and it is described in the views.actions_introduced list.
[
{
"version": "2.0.0",
"notes": [
"Added doggie detail view"
],
"models": [],
"views": {
"endpoints_introduced": [],
"endpoints_removed": [],
"actions_introduced": [
"DoggieViewSet.retrieve"
],
"actions_removed": []
}
},
{
"version": "1.0.0",
"notes": [
"Initial version :)"
],
"models": [],
"views": {
"endpoints_introduced": [],
"endpoints_removed": [],
"actions_introduced": [],
"actions_removed": []
}
}
]
The versioned_view decorator hides the view for requests with version < 2.0.0. We can demonstrate this by requesting GET /doggies/1/ with Accept: application/json; version=1.0.0 in Postman. We get a 404 response with the following body:
{
"detail": "Not found."
}
If we repeat the same request with Accept: application/json; version=2.0.0, we are given access to the view:
{
"id": 1,
"name": "Biko",
"birthday": "2023-01-30"
}
The versioned_view decorator also accepts a removed_in argument. If this is present, the view will be hidden for all requests whose version is greater.
ViewSets
If we want to introduce / remove a whole endpoint, we can achieve this by inheriting from the VersionedViewSet class. In this case the introduced_in and removed_in versions are set as class attributes, which also apply to any of the ViewSet's methods:
class CatViewSet(VersionedViewSet, viewsets.ReadOnlyModelViewSet):
serializer_class = CatSerializer
queryset = Cat.objects.all()
introduced_in = versions.VERSION_1_0_0
removed_in = versions.VERSION_5_0_0
@versioned_view(introduced_in=versions.VERSION_3_0_0)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@versioned_view(removed_in=versions.VERSION_4_0_0)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
However, individual methods can be further limited by using the versioned_view decorator. The most restrictive combination of viewset / method versions will be chosen. In this example this results in:
CatViewSetis available from 1.0.0 to 5.0.0retrieveis available from 3.0.0 to 5.0.0listis available from 1.0.0 to 4.0.0
The VersionedViewSet class also informs the relevant Version instances about its introduction and removal. It appears under views.endpoints_introduced / views.endpoints_removed in a serialized Version:
{
"version": "1.0.0",
"notes": [
"Initial version :)"
],
"models": [],
"views": {
"endpoints_introduced": [
"CatViewSet"
],
"endpoints_removed": [],
"actions_introduced": [],
"actions_removed": []
}
}