Advertisement
arctangent

Django Class-based Views - Early Experiments

Dec 6th, 2011
410
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.66 KB | None | 0 0
  1. from django.views.generic.base import View, TemplateResponseMixin, TemplateView
  2. from django.template import RequestContext
  3.  
  4. import csv
  5. from django.http import *
  6. from django.core.exceptions import PermissionDenied, ImproperlyConfigured
  7.  
  8. from django.utils.decorators import method_decorator
  9.  
  10.  
  11. # Jacob Conrad Martin
  12. # http://jacobconradmartin.com
  13.  
  14.  
  15.  
  16. # ---------------------------------------------
  17. # SIMPLEST POSSIBLE USAGE
  18. # ---------------------------------------------
  19.  
  20. class Home(TemplateView):
  21.     """
  22.    This uses the bog-standard TemplateView class.
  23.    This class provides a really simple way to display a template with some context.
  24.    """
  25.    
  26.     template_name = 'home.html'
  27.    
  28.     def get_context_data(request):
  29.         context = {}
  30.         return context
  31.  
  32.  
  33. # ---------------------------------------------
  34. # EXPERIMENT ONE: MORE REALISTIC EXAMPLE
  35. # ---------------------------------------------        
  36.        
  37. class MultipleResponseBaseView(TemplateResponseMixin, View):
  38.     """
  39.    This is a minimal example of a view which can be inherited from to provide
  40.    separated responses to HTTP GET and POST (etc.) requests. Derived classes
  41.    should specify get(), post() etc. methods in order to handle them.
  42.    
  43.    The TemplateResponseMixin class provides a render_to_response() method which
  44.    renders the given template_name within the provided context, i.e. you
  45.    automatically get the old RequestContext functionality without having
  46.    to type it out every time.
  47.    
  48.    The View class provides the as_view() method required in urls.py and also
  49.    the machinery to ensure that HTTP requests are dispatched to get(), post(),
  50.    and so on.
  51.    
  52.    For further insights, read the Django source code for these classes here:
  53.    https://code.djangoproject.com/browser/django/tags/releases/1.3/django/views/generic/base.py
  54.    
  55.    """
  56.    
  57.     template_name = None
  58.    
  59.     def render(self, context={}):
  60.         """
  61.        This wrapper function allows a shorthand way to render a response.
  62.        """
  63.        
  64.         return self.render_to_response(context)
  65.    
  66.        
  67. class ExperimentOne(MultipleResponseBaseView):
  68.     """
  69.    Demonstrates how to derive from the MultipleResponseBaseClass in order to
  70.    handle GET and POST requests. Other types of request will fail via the
  71.    http_method_not_allowed() method in the View grandparent class.
  72.    """
  73.  
  74.     template_name = 'experiment_one.html'
  75.    
  76.     def get(self, request):
  77.         return self.render()
  78.        
  79.     def post(self, request):
  80.         person_name = request.POST.get('person_name', None)
  81.         return self.render(locals())
  82.  
  83.  
  84. # ---------------------------------------------
  85. # EXPERIMENT TWO: AUTOMAGICALLY DOING CSV OUTPUT
  86. # ---------------------------------------------    
  87.  
  88. class CSVResponseMixin(object):
  89.     """
  90.    Provides a render_to_csv() method which will render out the context to CSV.
  91.    We expect that the supplied context consists of:
  92.        - filename  (a string)
  93.        - headings  (a list of strings)
  94.        - rows      (a list of lists of strings)
  95.    """
  96.    
  97.     def render_to_csv(self, context):
  98.         # Get the data to be rendered as CSV.
  99.         try:
  100.             csv_context = context['csv']
  101.         except:
  102.             raise ImproperlyConfigured('The supplied context does not contain an key called "csv".')
  103.         # Get the filename, headings and rows to use
  104.         try:
  105.             filename = csv_context['filename']
  106.             headings = csv_context['headings']
  107.             rows = csv_context['rows']
  108.         except:
  109.             raise ImproperlyConfigured('You must specify "filename", "headings" and "rows" within the "csv" dict.')
  110.            
  111.         # Initial setup
  112.         charset = 'windows-1252'
  113.         response = HttpResponse(mimetype='text/csv; charset=%s' % charset)
  114.         response['Content-Disposition'] = 'attachment; filename=%s' % filename
  115.         w = csv.writer(response)
  116.         # Headings
  117.         headings = [unicode(c).encode(charset, 'replace') for c in headings]
  118.         w.writerow(headings)
  119.         # Rows
  120.         for row in rows:
  121.             row = [unicode(c).encode(charset, 'replace') for c in row]
  122.             w.writerow(row)
  123.         # Done
  124.         return response
  125.  
  126.        
  127. class ExperimentTwo(MultipleResponseBaseView, CSVResponseMixin):
  128.     """
  129.    Demonstrates how to derive from the MultipleResponseBaseClass in order to
  130.    handle GET and POST requests. Other types of request will fail via the
  131.    http_method_not_allowed() method in the View grandparent class.
  132.    """
  133.  
  134.     template_name = 'experiment_two.html'
  135.    
  136.     def get(self, request):
  137.         return self.render()
  138.        
  139.     def post(self, request):
  140.         csv = {
  141.             'filename': 'awesome.csv',
  142.             'headings': ['foo', 'bar', 'baz'],
  143.             'rows': [ [1,2,4], [2,4,6], [4,8,16] ]
  144.         }
  145.         context = { 'csv': csv }
  146.         return self.render_to_csv(context)
  147.        
  148.  
  149. # ---------------------------------------------
  150. # EXPERIMENT THREE: AUTHENTICATION MIXIN
  151. # ---------------------------------------------  
  152.  
  153. class SuperuserRequiredMixin(object):
  154.     """
  155.    Demonstrates how you go about packing all your authentication into a mixin.
  156.    Here we just test that the user is logged in and is a superuser. Derived
  157.    classes must ensure that they always call the get() method defined below,
  158.    i.e. by using super().
  159.    FIXME: Is there some way to make this happen automatically?
  160.    """
  161.    
  162.     def get(self, request):
  163.         if not request.user.is_superuser:
  164.             raise PermissionDenied
  165.            
  166.  
  167. class ExperimentThree(MultipleResponseBaseView, SuperuserRequiredMixin):
  168.     """
  169.    Example of how to use the authentication mixin above.
  170.    Note that classes deriving from SuperuserRequiredMixin must always
  171.    ensure that they call the superclass method or it will not execute.
  172.    """
  173.    
  174.     template_name = 'experiment_three.html'
  175.    
  176.     def get(self, request):
  177.         # Ensure that we have call the get() method of the superclass
  178.         super(ExperimentThree, self).get(request)
  179.         # If the superclass didn't raise PermissionDenied then the following code will execute
  180.         return self.render()
  181.  
  182.  
  183. # ---------------------------------------------
  184. # EXPERIMENT FOUR: FUNCTIONALITY MIXIN (A.K.A. BOILERPLATE REDUCTION MIXIN)
  185. # ---------------------------------------------  
  186.  
  187.  
  188. class FooObjectRequiredMixin(object):
  189.     """
  190.    Demonstrates how you would go about performing generic get_object_or_404
  191.    type of behaviour in a transparent way for lots of different but similar views.
  192.    You can use this technique to perform all sorts of boilerplate activities
  193.    so that your derived classes (i.e. views which map to URLs) can be as
  194.    simple as possible.
  195.    """
  196.    
  197.     def get_foo_or_404(self, foo_id):
  198.         # We're not hooking this up to a database so instead we'll just pretend
  199.         # that odd-numbered IDs exist and even-numbered IDs do not exist.
  200.         if int(foo_id) % 2 == 0:
  201.             return 'Foo Object ' + foo_id
  202.         else:
  203.             raise Http404
  204.  
  205.  
  206. class ExperimentFour(MultipleResponseBaseView, FooObjectRequiredMixin):
  207.     """
  208.    Example of how to use the functionality mixin above.
  209.    """
  210.    
  211.     template_name = 'experiment_four.html'
  212.    
  213.     def get(self, request, foo_id=None):
  214.         # Show the homepage for the experiment if a value for foo_id was not specified
  215.         if foo_id == None:
  216.             return self.render()
  217.         # The user has selected a foo_id, so let's try and fetch the appropriate object
  218.         # (If the user requests an odd-numbered foo_id they will get a 404 error by design)
  219.         foo = self.get_foo_or_404(foo_id)
  220.         # Render the template
  221.         return self.render(locals())
  222.  
  223.  
  224. # ---------------------------------------------
  225. # EXPERIMENT FIVE: IMPLICIT AUTHENTICATION MIXIN
  226. # ---------------------------------------------  
  227.  
  228. class ViewWithRenderShortCut(View):
  229.  
  230.     template_name = None
  231.    
  232.     def render(self, context={}):
  233.         """
  234.        This wrapper function allows a shorthand way to render a response.
  235.        """
  236.        
  237.         return self.render_to_response(context)
  238.  
  239.  
  240.        
  241. class ImplicitSuperUserRequiredView(ViewWithRenderShortCut):
  242.     """
  243.    Requires a superuser for all HTTP request verb.
  244.    This could be easily customised to do something different for each verb.
  245.    Or even to do things like fetch an object if given its ID (or return a 404).
  246.    """
  247.  
  248.     def dispatch(self, request, *args, **kwargs):
  249.         """
  250.        Hijack the dispatch() method of the View class to ensure that the user is a superuser
  251.        before dispatching the HTTP request off to a method matching the HTTP verb.
  252.        """
  253.        
  254.         if not request.user.is_superuser:
  255.             # Django can't do "raise Http 403" just yet.
  256.             raise PermissionDenied
  257.         else:
  258.             # This is just a demo of creating a variable within a scope available to derived classes.
  259.             # In real apps, you'd probably want to do existence checks on objects here, e.g. if the
  260.             # request signature contained a reference to a supplier_id then you could fetch the
  261.             # supplier object into score or raise a 404 if it could not be located. This would avoid
  262.             # boilerplate code being used everywhere.
  263.             self.lucky_number = '42'
  264.        
  265.         return super(ImplicitSuperUserRequiredView, self).dispatch(request, *args, **kwargs)
  266.  
  267.  
  268. class ExperimentFive(TemplateResponseMixin, ImplicitSuperUserRequiredView):
  269.     """
  270.    Example of how to use the implicitly applied authentication mixin above.
  271.    """
  272.    
  273.     template_name = 'experiment_five.html'
  274.    
  275.     def get(self, request):
  276.         # The dispatch method has already been hijacked, so the next
  277.         # line should only run if the user is a superuser.
  278.         lucky_number = self.lucky_number
  279.         return self.render(locals())
  280.        
  281.  
  282.        
  283. # ---------------------------------------------
  284. # IMPLICIT FUNCTIONALITY MIXIN (A.K.A. BOILERPLATE REDUCTION MIXIN)
  285. # ---------------------------------------------  
  286.  
  287.        
  288. class ImplicitObjectExistenceEnforcerView(ViewWithRenderShortCut):
  289.     """
  290.    Example of how to extend the concept of implicitly applied authentication to
  291.    achieve implicitly extended functionality, specifically to automatically
  292.    ensure the existence of objects referred to in the method signatures of
  293.    derived classes.
  294.    NOTE: This required the ViewWithRenderShortcut defined above.
  295.    """
  296.    
  297.     def dispatch(self, request, *args, **kwargs):
  298.         """
  299.        The general strategy here is to look for known (agreed) kwargs and try
  300.        to fetch the relevant objects into scope. If they do not exist then a
  301.        Http404 error is returned instead. Note that we could generalise this
  302.        approach to handle any value supplied in kwargs, but IMHO it is better
  303.        to be explicit about which objects you provide this functionality for.
  304.        """
  305.    
  306.         # Was a foo_id supplied?
  307.         self.foo = None
  308.         if 'foo_id' in kwargs:
  309.             # If so, ensure it exists. (Note: We'd do a DB lookup in the real world)
  310.             foo_id = int(kwargs['foo_id'])
  311.             if foo_id == 2: self.foo = 'I am foo #2'
  312.             else: raise Http404
  313.            
  314.         # Was a bar_id supplied?
  315.         self.bar = None
  316.         if 'bar_id' in kwargs:
  317.             # If so, ensure it exists. (Note: We'd do a DB lookup in the real world)
  318.             foo_id = int(kwargs['bar_id'])
  319.             if foo_id == 2: self.bar = 'I am bar #2'
  320.             else: raise Http404
  321.        
  322.         return super(ImplicitObjectExistenceEnforcerView, self).dispatch(request, *args, **kwargs)
  323.  
  324.  
  325. class ExperimentSix(TemplateResponseMixin, ImplicitObjectExistenceEnforcerView):
  326.     """
  327.    This view can be incredibly simple because the machinery to check for object
  328.    existence (and act accordingly if the objects do not exist) is neatly tucked
  329.    away in the superclass.
  330.    """
  331.    
  332.     template_name = 'experiment_six.html'
  333.    
  334.     def get(self, request, foo_id=None, bar_id=None):
  335.         """
  336.        Note that by specifying foo_id and bar_id in the method signature above we can be assured that
  337.        there will be a self.foo object and a self.bar object for the remainder of our code, because the
  338.        ImplicitObjectExistenceEnforcerView view contains the relevant machinery to make this happen or
  339.        raise a Http404 accordingly.
  340.        """
  341.        
  342.         foo = self.foo
  343.         bar = self.bar
  344.        
  345.         return self.render(locals())
  346.        
  347.  
  348.  
  349.  
  350.  
  351.  
  352.  
  353.  
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement