Merge branch 'release/0.9.2'
[philo.git] / philo / middleware.py
1 from django.conf import settings
2 from django.contrib.sites.models import Site
3 from django.http import Http404
4
5 from philo.models import Node, View
6 from philo.utils.lazycompat import SimpleLazyObject
7
8
9 def get_node(path):
10         """Returns a :class:`Node` instance at ``path`` (relative to the current site) or ``None``."""
11         try:
12                 current_site = Site.objects.get_current()
13         except Site.DoesNotExist:
14                 current_site = None
15         
16         trailing_slash = False
17         if path[-1] == '/':
18                 trailing_slash = True
19         
20         try:
21                 node, subpath = Node.objects.get_with_path(path, root=getattr(current_site, 'root_node', None), absolute_result=False)
22         except Node.DoesNotExist:
23                 return None
24         
25         if subpath is None:
26                 subpath = ""
27         subpath = "/" + subpath
28         
29         if trailing_slash and subpath[-1] != "/":
30                 subpath += "/"
31         
32         node._path = path
33         node._subpath = subpath
34         
35         return node
36
37
38 class RequestNodeMiddleware(object):
39         """
40         Adds a ``node`` attribute, representing the currently-viewed :class:`.Node`, to every incoming :class:`HttpRequest` object. This is required by :func:`philo.views.node_view`.
41         
42         :class:`RequestNodeMiddleware` also catches all exceptions raised while handling requests that have attached :class:`.Node`\ s if :setting:`settings.DEBUG` is ``True``. If a :exc:`django.http.Http404` error was caught, :class:`RequestNodeMiddleware` will look for an "Http404" :class:`.Attribute` on the request's :class:`.Node`; otherwise it will look for an "Http500" :class:`.Attribute`. If an appropriate :class:`.Attribute` is found, and the value of the attribute is a :class:`.View` instance, then the :class:`.View` will be rendered with the exception in the ``extra_context``, bypassing any later handling of exceptions.
43         
44         """
45         def process_view(self, request, view_func, view_args, view_kwargs):
46                 try:
47                         path = view_kwargs['path']
48                 except KeyError:
49                         request.node = None
50                 else:
51                         request.node = SimpleLazyObject(lambda: get_node(path))
52         
53         def process_exception(self, request, exception):
54                 if settings.DEBUG or not hasattr(request, 'node') or not request.node:
55                         return
56                 
57                 if isinstance(exception, Http404):
58                         error_view = request.node.attributes.get('Http404', None)
59                         status_code = 404
60                 else:
61                         error_view = request.node.attributes.get('Http500', None)
62                         status_code = 500
63                 
64                 if error_view is None or not isinstance(error_view, View):
65                         # Should this be duck-typing? Perhaps even no testing?
66                         return
67                 
68                 extra_context = {'exception': exception}
69                 response = error_view.render_to_response(request, extra_context)
70                 response.status_code = status_code
71                 return response