Introduction
As your API grows, function-based views with long if/elif chains for HTTP methods become unwieldy. Class-based views (CBVs) offer a cleaner, object-oriented approach where each HTTP method maps to a class method. This separation makes code easier to read, test, and extend.
Key Concepts
Class-Based View (CBV): A view defined as a Python class that inherits from django.views.View. HTTP methods (GET, POST, etc.) are handled by corresponding methods (get(), post()).
dispatch() method: The entry point that routes requests to the appropriate handler method based on request.method.
as_view() class method: Converts the class into a callable view function for URL routing.
Mixins: Reusable classes that add functionality to views through multiple inheritance.
Real World Context
In production Django applications, CBVs are preferred for APIs because:
- Organization: Each HTTP method has its own method—no more long if/elif chains
- Reusability: Common patterns can be extracted into mixins
- Testability: Individual methods can be tested in isolation
- Extensibility: Subclasses can override specific behaviors
Deep Dive
Basic API View Structure
This class maps each HTTP method to a descriptive handler method, keeping the routing logic clean and each operation isolated:
pythonimport json from django.http import HttpResponse, JsonResponse from django.views import View from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator @method_decorator(csrf_exempt, name='dispatch') class ArticleAPIView(View): """RESTful API view for Article resource.""" def get(self, request, pk=None): if pk: return self.retrieve(request, pk) return self.list(request) def post(self, request): return self.create(request) def put(self, request, pk): return self.update(request, pk) def delete(self, request, pk): return self.destroy(request, pk) # Implementation methods def list(self, request): articles = Article.objects.all()[:20] return JsonResponse({'data': list(articles.values('id', 'title'))}) def retrieve(self, request, pk): try: article = Article.objects.get(pk=pk) except Article.DoesNotExist: return JsonResponse({'error': 'Not found'}, status=404) return JsonResponse({'id': article.id, 'title': article.title}) def create(self, request): data = json.loads(request.body) article = Article.objects.create(title=data['title']) return JsonResponse({'id': article.id}, status=201) def update(self, request, pk): article = Article.objects.get(pk=pk) data = json.loads(request.body) article.title = data['title'] article.save() return JsonResponse({'id': article.id}) def destroy(self, request, pk): Article.objects.filter(pk=pk).delete() return HttpResponse(status=204)
The @method_decorator(csrf_exempt, name='dispatch') applies CSRF exemption to all methods at once by targeting the dispatch() entry point. Each handler method like list() and create() reads clearly on its own.
URL Configuration
Connect the class-based view to URLs by calling .as_view(), which returns a callable that Django's URL dispatcher can use:
pythonfrom django.urls import path from .views import ArticleAPIView urlpatterns = [ path('api/articles/', ArticleAPIView.as_view(), name='article-list'), path('api/articles/<int:pk>/', ArticleAPIView.as_view(), name='article-detail'), ]
Both URL patterns point to the same view class. When pk is present, the get() method routes to retrieve(); otherwise it calls list().
Creating Reusable Mixins
Mixins let you extract common behavior into small, focused classes that can be combined through multiple inheritance:
pythonclass JSONMixin: """Mixin for JSON handling.""" def parse_json(self): try: return json.loads(self.request.body) except json.JSONDecodeError: return None def json_response(self, data, status=200): return JsonResponse(data, status=status) def error_response(self, message, status=400): return JsonResponse({'error': message}, status=status) class CRUDMixin: """Mixin providing CRUD operations.""" model = None fields = [] def get_queryset(self): return self.model.objects.all() def serialize(self, obj): return {f: getattr(obj, f) for f in self.fields}
Each mixin handles a single concern: JSONMixin handles parsing and response formatting, while CRUDMixin provides queryset access and serialization.
Combining Mixins
With both mixins in place, the final view class only needs to declare its model and fields -- all the CRUD logic is inherited:
python@method_decorator(csrf_exempt, name='dispatch') class ArticleAPIView(JSONMixin, CRUDMixin, View): model = Article fields = ['id', 'title', 'body', 'created_at'] def get(self, request, pk=None): if pk: obj = self.get_queryset().filter(pk=pk).first() if not obj: return self.error_response('Not found', 404) return self.json_response(self.serialize(obj)) return self.json_response({'data': [self.serialize(o) for o in self.get_queryset()[:20]]})
In Python's MRO (Method Resolution Order), mixins listed first take precedence. Here, JSONMixin provides error_response() and json_response(), while CRUDMixin provides get_queryset() and serialize().
Common Pitfalls
-
Forgetting
as_view()in URLs:path('api/', MyView)won't work. You must callMyView.as_view()to convert the class to a callable. -
Decorator placement: Using
@csrf_exemptdirectly on the class doesn't work. Use@method_decorator(csrf_exempt, name='dispatch')on the class. -
Not handling all methods: If you define
get()but notpost(), POST requests return 405 automatically—which is correct behavior.
Best Practices
-
Separate concerns: Put business logic in model methods or service classes, not in view methods.
-
Use descriptive method names:
list(),retrieve(),create(),update(),destroy()are clearer than justget(),post(). -
Create a base API view: Define common behavior once in a base class that all your API views inherit from.
-
Keep mixins focused: Each mixin should do one thing well (JSON handling, authentication, pagination).
Summary
Class-based views organize API code by mapping HTTP methods to class methods. Use @method_decorator to apply decorators, call .as_view() in URL configuration, and extract reusable functionality into mixins. CBVs provide better organization, testability, and extensibility compared to function-based views for complex APIs.
Code Examples
from django.views import View
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@method_decorator(csrf_exempt, name='dispatch')
class ArticleAPIView(View):
def get(self, request, pk=None):
if pk:
article = Article.objects.get(pk=pk)
return JsonResponse({'id': article.id, 'title': article.title})
articles = Article.objects.all()[:20]
return JsonResponse({'data': list(articles.values('id', 'title'))})
def post(self, request):
data = json.loads(request.body)
article = Article.objects.create(title=data['title'])
return JsonResponse({'id': article.id}, status=201)