from django.views.generic.base import View, TemplateResponseMixin, TemplateView
from django.template import RequestContext
import csv
from django.http import *
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from django.utils.decorators import method_decorator
# Jacob Conrad Martin
# http://jacobconradmartin.com
# ---------------------------------------------
# SIMPLEST POSSIBLE USAGE
# ---------------------------------------------
class Home(TemplateView):
"""
This uses the bog-standard TemplateView class.
This class provides a really simple way to display a template with some context.
"""
template_name = 'home.html'
def get_context_data(request):
context = {}
return context
# ---------------------------------------------
# EXPERIMENT ONE: MORE REALISTIC EXAMPLE
# ---------------------------------------------
class MultipleResponseBaseView(TemplateResponseMixin, View):
"""
This is a minimal example of a view which can be inherited from to provide
separated responses to HTTP GET and POST (etc.) requests. Derived classes
should specify get(), post() etc. methods in order to handle them.
The TemplateResponseMixin class provides a render_to_response() method which
renders the given template_name within the provided context, i.e. you
automatically get the old RequestContext functionality without having
to type it out every time.
The View class provides the as_view() method required in urls.py and also
the machinery to ensure that HTTP requests are dispatched to get(), post(),
and so on.
For further insights, read the Django source code for these classes here:
https://code.djangoproject.com/browser/django/tags/releases/1.3/django/views/generic/base.py
"""
template_name = None
def render(self, context={}):
"""
This wrapper function allows a shorthand way to render a response.
"""
return self.render_to_response(context)
class ExperimentOne(MultipleResponseBaseView):
"""
Demonstrates how to derive from the MultipleResponseBaseClass in order to
handle GET and POST requests. Other types of request will fail via the
http_method_not_allowed() method in the View grandparent class.
"""
template_name = 'experiment_one.html'
def get(self, request):
return self.render()
def post(self, request):
person_name = request.POST.get('person_name', None)
return self.render(locals())
# ---------------------------------------------
# EXPERIMENT TWO: AUTOMAGICALLY DOING CSV OUTPUT
# ---------------------------------------------
class CSVResponseMixin(object):
"""
Provides a render_to_csv() method which will render out the context to CSV.
We expect that the supplied context consists of:
- filename (a string)
- headings (a list of strings)
- rows (a list of lists of strings)
"""
def render_to_csv(self, context):
# Get the data to be rendered as CSV.
try:
csv_context = context['csv']
except:
raise ImproperlyConfigured('The supplied context does not contain an key called "csv".')
# Get the filename, headings and rows to use
try:
filename = csv_context['filename']
headings = csv_context['headings']
rows = csv_context['rows']
except:
raise ImproperlyConfigured('You must specify "filename", "headings" and "rows" within the "csv" dict.')
# Initial setup
charset = 'windows-1252'
response = HttpResponse(mimetype='text/csv; charset=%s' % charset)
response['Content-Disposition'] = 'attachment; filename=%s' % filename
w = csv.writer(response)
# Headings
headings = [unicode(c).encode(charset, 'replace') for c in headings]
w.writerow(headings)
# Rows
for row in rows:
row = [unicode(c).encode(charset, 'replace') for c in row]
w.writerow(row)
# Done
return response
class ExperimentTwo(MultipleResponseBaseView, CSVResponseMixin):
"""
Demonstrates how to derive from the MultipleResponseBaseClass in order to
handle GET and POST requests. Other types of request will fail via the
http_method_not_allowed() method in the View grandparent class.
"""
template_name = 'experiment_two.html'
def get(self, request):
return self.render()
def post(self, request):
csv = {
'filename': 'awesome.csv',
'headings': ['foo', 'bar', 'baz'],
'rows': [ [1,2,4], [2,4,6], [4,8,16] ]
}
context = { 'csv': csv }
return self.render_to_csv(context)
# ---------------------------------------------
# EXPERIMENT THREE: AUTHENTICATION MIXIN
# ---------------------------------------------
class SuperuserRequiredMixin(object):
"""
Demonstrates how you go about packing all your authentication into a mixin.
Here we just test that the user is logged in and is a superuser. Derived
classes must ensure that they always call the get() method defined below,
i.e. by using super().
FIXME: Is there some way to make this happen automatically?
"""
def get(self, request):
if not request.user.is_superuser:
raise PermissionDenied
class ExperimentThree(MultipleResponseBaseView, SuperuserRequiredMixin):
"""
Example of how to use the authentication mixin above.
Note that classes deriving from SuperuserRequiredMixin must always
ensure that they call the superclass method or it will not execute.
"""
template_name = 'experiment_three.html'
def get(self, request):
# Ensure that we have call the get() method of the superclass
super(ExperimentThree, self).get(request)
# If the superclass didn't raise PermissionDenied then the following code will execute
return self.render()
# ---------------------------------------------
# EXPERIMENT FOUR: FUNCTIONALITY MIXIN (A.K.A. BOILERPLATE REDUCTION MIXIN)
# ---------------------------------------------
class FooObjectRequiredMixin(object):
"""
Demonstrates how you would go about performing generic get_object_or_404
type of behaviour in a transparent way for lots of different but similar views.
You can use this technique to perform all sorts of boilerplate activities
so that your derived classes (i.e. views which map to URLs) can be as
simple as possible.
"""
def get_foo_or_404(self, foo_id):
# We're not hooking this up to a database so instead we'll just pretend
# that odd-numbered IDs exist and even-numbered IDs do not exist.
if int(foo_id) % 2 == 0:
return 'Foo Object ' + foo_id
else:
raise Http404
class ExperimentFour(MultipleResponseBaseView, FooObjectRequiredMixin):
"""
Example of how to use the functionality mixin above.
"""
template_name = 'experiment_four.html'
def get(self, request, foo_id=None):
# Show the homepage for the experiment if a value for foo_id was not specified
if foo_id == None:
return self.render()
# The user has selected a foo_id, so let's try and fetch the appropriate object
# (If the user requests an odd-numbered foo_id they will get a 404 error by design)
foo = self.get_foo_or_404(foo_id)
# Render the template
return self.render(locals())
# ---------------------------------------------
# EXPERIMENT FIVE: IMPLICIT AUTHENTICATION MIXIN
# ---------------------------------------------
class ViewWithRenderShortCut(View):
template_name = None
def render(self, context={}):
"""
This wrapper function allows a shorthand way to render a response.
"""
return self.render_to_response(context)
class ImplicitSuperUserRequiredView(ViewWithRenderShortCut):
"""
Requires a superuser for all HTTP request verb.
This could be easily customised to do something different for each verb.
Or even to do things like fetch an object if given its ID (or return a 404).
"""
def dispatch(self, request, *args, **kwargs):
"""
Hijack the dispatch() method of the View class to ensure that the user is a superuser
before dispatching the HTTP request off to a method matching the HTTP verb.
"""
if not request.user.is_superuser:
# Django can't do "raise Http 403" just yet.
raise PermissionDenied
else:
# This is just a demo of creating a variable within a scope available to derived classes.
# In real apps, you'd probably want to do existence checks on objects here, e.g. if the
# request signature contained a reference to a supplier_id then you could fetch the
# supplier object into score or raise a 404 if it could not be located. This would avoid
# boilerplate code being used everywhere.
self.lucky_number = '42'
return super(ImplicitSuperUserRequiredView, self).dispatch(request, *args, **kwargs)
class ExperimentFive(TemplateResponseMixin, ImplicitSuperUserRequiredView):
"""
Example of how to use the implicitly applied authentication mixin above.
"""
template_name = 'experiment_five.html'
def get(self, request):
# The dispatch method has already been hijacked, so the next
# line should only run if the user is a superuser.
lucky_number = self.lucky_number
return self.render(locals())
# ---------------------------------------------
# IMPLICIT FUNCTIONALITY MIXIN (A.K.A. BOILERPLATE REDUCTION MIXIN)
# ---------------------------------------------
class ImplicitObjectExistenceEnforcerView(ViewWithRenderShortCut):
"""
Example of how to extend the concept of implicitly applied authentication to
achieve implicitly extended functionality, specifically to automatically
ensure the existence of objects referred to in the method signatures of
derived classes.
NOTE: This required the ViewWithRenderShortcut defined above.
"""
def dispatch(self, request, *args, **kwargs):
"""
The general strategy here is to look for known (agreed) kwargs and try
to fetch the relevant objects into scope. If they do not exist then a
Http404 error is returned instead. Note that we could generalise this
approach to handle any value supplied in kwargs, but IMHO it is better
to be explicit about which objects you provide this functionality for.
"""
# Was a foo_id supplied?
self.foo = None
if 'foo_id' in kwargs:
# If so, ensure it exists. (Note: We'd do a DB lookup in the real world)
foo_id = int(kwargs['foo_id'])
if foo_id == 2: self.foo = 'I am foo #2'
else: raise Http404
# Was a bar_id supplied?
self.bar = None
if 'bar_id' in kwargs:
# If so, ensure it exists. (Note: We'd do a DB lookup in the real world)
foo_id = int(kwargs['bar_id'])
if foo_id == 2: self.bar = 'I am bar #2'
else: raise Http404
return super(ImplicitObjectExistenceEnforcerView, self).dispatch(request, *args, **kwargs)
class ExperimentSix(TemplateResponseMixin, ImplicitObjectExistenceEnforcerView):
"""
This view can be incredibly simple because the machinery to check for object
existence (and act accordingly if the objects do not exist) is neatly tucked
away in the superclass.
"""
template_name = 'experiment_six.html'
def get(self, request, foo_id=None, bar_id=None):
"""
Note that by specifying foo_id and bar_id in the method signature above we can be assured that
there will be a self.foo object and a self.bar object for the remainder of our code, because the
ImplicitObjectExistenceEnforcerView view contains the relevant machinery to make this happen or
raise a Http404 accordingly.
"""
foo = self.foo
bar = self.bar
return self.render(locals())