Access the Django Views - The Right Way guide
masterThis project is a documentation-only resource providing an opinionated guide on how to implement Django views correctly. You can access the full documentation online at the following URL:
repository·master·Indexed 41 days ago
https://github.com/spookylukey/django-views-the-right-wayA reference implementation and opinionated guide on implementing Django views. It advocates for Function-Based Views (FBVs) as a starting point, prefers functional composition over Class-Based View (CBV) mixins, and provides patterns for delegation, common context data handling, and parameterized generic views to avoid premature abstraction.
This project is a documentation-only resource providing an opinionated guide on how to implement Django views correctly. You can access the full documentation online at the following URL:
For large-scale projects with highly repetitive views (like CRUD operations), move beyond individual view functions and provide abstractions for sets of views.
To succeed with high-level abstractions (similar to how Django Admin or Django Rest Framework operate), follow these principles:
serializers, permissions, or filters rather than inheriting their logic.When implementing custom logic in a view, prioritize direct implementation over forcing code into a specific pattern or framework abstraction. The project advocates for a 'just do it' approach:
To maintain a clean architecture, views should be kept 'skinny' by delegating business logic to the model layer.
What a view should do:
What a view should NOT do:
Note on UI concerns: Logic related to the web interface, such as messages.info(), should remain in the view layer, as it is specific to the HTTP context.
While Django Mixins (like LoginRequiredMixin) can be used in Class-Based Views (CBVs) by adding them to the inheritance list, they do not always compose reliably.
The Risk: If multiple mixins rely on the same method (e.g., overriding dispatch()) and do not use super() correctly, or if they use specific implementations like Django's UserPassesTestMixin (which cannot be stacked), they may silently fail. In such cases, only one of the checks might actually run, potentially creating security vulnerabilities.
Best Practice:
Depending on the scope and cost of the data, choose one of the following Django patterns:
To avoid the 'bare view' problem of django-decorator-include, you can use a two-step pattern:
django-decorator-include that does nothing at runtime but checks at import time if the required attribute is present on the view. If the attribute is missing, it raises an AssertionError.This ensures that if you forget to decorate a view, the application fails to start, preventing security holes from reaching production.
Implementation Example:
_SECURITY_POLICY_APPLIED = "_SECURITY_POLICY_APPLIED"
# 1. The Checker
def check_security_policy_applied(view_func):
if not getattr(view_func, _SECURITY_POLICY_APPLIED, False):
raise AssertionError(f"{view_func.__module__}.{view_func.__name__} needs to have a security policy applied")
return view_func
# 2. The Policy
def premium_required(view_func):
@functools.wraps(view_func)
def wrapper(request, *args, **kwargs):
if not (request.user.is_authenticated and request.user.is_premium):
# ... handle unauthorized access ...
return HttpResponseRedirect('/')
return view_func(request, *args, **kwargs)
setattr(wrapper, _SECURITY_POLICY_APPLIED, True)
return wrapper
# 3. Usage in urls.py
urlpatterns = [
include('myapp.urls', decorators=[check_security_policy_applied]),
]Complex Class-Based View (CBV) hierarchies involving multiple mixins can lead to Method Resolution Order (MRO) issues, sometimes requiring custom metaclasses (like AjaxMroFixer) to reorder how methods like post are inherited.
To avoid the need for 'metaclass fixes' and to make code easier to maintain, consider refactoring complex CBVs into Function-Based Views (FBVs). An FBV can explicitly handle form flow control, request methods, and template responses without delegating to a deep chain of base classes.
@booking_account_required
@ajax_form_validate(AccountDetailsForm)
def account_details(request):
form_class = AccountDetailsForm
if request.method == "POST":
form = form_class(request.POST, instance=request.booking_account)
if form.is_valid():
form.save()
messages.info(request, 'Account details updated, thank you.')
return next_step(request.booking_account)
else:
form = form_class(instance=request.booking_account)
return TemplateResponse(request, 'cciw/bookings/account_details.html', {
'title': 'Booking - account details',
'stage': BookingStage.ACCOUNT,
'form': form,
})The project advocates for Function-Based Views (FBVs) over Class-Based Views (CBVs) like TemplateView or ListView to maintain visibility of the core Django request-response cycle.
| Feature | Function-Based View (FBV) | Class-Based View (CBV) |
|---|---|---|
| Core Nature | A clear Python function/callable. | A class that requires .as_view() to become a callable. |
| Visibility | Request and Response are explicit in the signature. | Request and Response are obscured by inheritance. |
| Complexity | Simple and direct. | High inheritance trees and complex method flows. |
| Context Data | Passed directly via a dictionary. | Often requires more boilerplate to implement. |
FBV Example:
def my_view(request):
return TemplateResponse(request, 'template.html', {'data': 'value'})CBV Example (Obscured):
from django.views.generic import TemplateView
class ExampleView(TemplateView):
template_name = "example.html"When defining views, prefer using the queryset attribute over the model attribute.
Using queryset = Product.objects.all() provides a more flexible starting point than model = Product. If requirements change (e.g., you need to filter for only 'visible' products), you can easily update the queryset to Product.objects.visible() without changing the fundamental structure of the view. This applies to both CBVs and Function-Based Views (FBVs) using get_object_or_404.
# In CBVs
# Avoid:
class ProductDetailView(DetailView):
model = Product
# Prefer:
class ProductDetailView(DetailView):
queryset = Product.objects.all()
# In FBVs
# Avoid:
get_object_or_404(Product, pk=pk)
# Prefer:
get_object_or_404(Product.objects.all(), pk=pk)When deciding whether to reuse existing code or implement your own, prioritize clarity and the correct mental model over avoiding code duplication.
Key principles:
To avoid keyword argument clashes in urls.py when using extra options, use a "view factory". A view factory is a function that accepts configuration parameters and returns a specific view function. This pattern is useful for creating many similar views that only differ by configuration.
Benefits:
urls.py kwargs and the view's own arguments.from django.urls import reverse
from django.http import HttpResponseRedirect
def make_redirector(pattern_name=None, permanent=False, query_string=False):
def redirector(request, *args, **kwargs):
url = reverse(pattern_name, args=args, kwargs=kwargs)
# Implementation of permanent/query_string logic goes here
return HttpResponseRedirect(url)
return redirector
# Usage in urls.py
urlpatterns = [
path('old-path/<int:pk>/', make_redirector(
pattern_name='my_view',
permanent=True,
query_string=True,
)),
]