Refactored RequestNodeMiddleware to use SimpleLazyObject - comparable to django r1629...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Wed, 1 Jun 2011 15:53:53 +0000 (11:53 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Wed, 1 Jun 2011 16:00:28 +0000 (12:00 -0400)
philo/contrib/penfield/models.py
philo/middleware.py
philo/models/nodes.py
philo/utils/lazycompat.py [new file with mode: 0644]
philo/views.py

index b8ca610..7a3eaa8 100644 (file)
@@ -201,7 +201,7 @@ class FeedView(MultiView):
                        language = settings.LANGUAGE_CODE.decode(),
                        feed_url = add_domain(
                                current_site.domain,
-                               self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node.subpath, with_domain=True, request=request, secure=request.is_secure()),
+                               self.__get_dynamic_attr('feed_url', obj) or node.construct_url(node._subpath, with_domain=True, request=request, secure=request.is_secure()),
                                request.is_secure()
                        ),
                        author_name = self.__get_dynamic_attr('author_name', obj),
index b90067a..037fdc8 100644 (file)
@@ -3,56 +3,47 @@ from django.contrib.sites.models import Site
 from django.http import Http404
 
 from philo.models import Node, View
+from philo.utils.lazycompat import SimpleLazyObject
 
 
-class LazyNode(object):
-       def __get__(self, request, obj_type=None):
-               if not hasattr(request, '_cached_node_path'):
-                       return None
-               
-               if not hasattr(request, '_found_node'):
-                       try:
-                               current_site = Site.objects.get_current()
-                       except Site.DoesNotExist:
-                               current_site = None
-                       
-                       path = request._cached_node_path
-                       trailing_slash = False
-                       if path[-1] == '/':
-                               trailing_slash = True
-                       
-                       try:
-                               node, subpath = Node.objects.get_with_path(path, root=getattr(current_site, 'root_node', None), absolute_result=False)
-                       except Node.DoesNotExist:
-                               node = None
-                       else:
-                               if subpath is None:
-                                       subpath = ""
-                               subpath = "/" + subpath
-                               
-                               if not node.handles_subpath(subpath):
-                                       node = None
-                               else:
-                                       if trailing_slash and subpath[-1] != "/":
-                                               subpath += "/"
-                                       
-                                       node.subpath = subpath
-                       
-                       request._found_node = node
-               
-               return request._found_node
+def get_node(path):
+       """Returns a :class:`Node` instance at ``path`` (relative to the current site) or ``None``."""
+       try:
+               current_site = Site.objects.get_current()
+       except Site.DoesNotExist:
+               current_site = None
+       
+       trailing_slash = False
+       if path[-1] == '/':
+               trailing_slash = True
+       
+       try:
+               node, subpath = Node.objects.get_with_path(path, root=getattr(current_site, 'root_node', None), absolute_result=False)
+       except Node.DoesNotExist:
+               return None
+       
+       if subpath is None:
+               subpath = ""
+       subpath = "/" + subpath
+       
+       if trailing_slash and subpath[-1] != "/":
+               subpath += "/"
+       
+       node._path = path
+       node._subpath = subpath
+       
+       return node
 
 
 class RequestNodeMiddleware(object):
        """Adds a ``node`` attribute, representing the currently-viewed node, to every incoming :class:`HttpRequest` object. This is required by :func:`philo.views.node_view`."""
-       def process_request(self, request):
-               request.__class__.node = LazyNode()
-       
        def process_view(self, request, view_func, view_args, view_kwargs):
                try:
-                       request._cached_node_path = view_kwargs['path']
+                       path = view_kwargs['path']
                except KeyError:
-                       pass
+                       request.node = None
+               else:
+                       request.node = SimpleLazyObject(lambda: get_node(path))
        
        def process_exception(self, request, exception):
                if settings.DEBUG or not hasattr(request, 'node') or not request.node:
index 87ccb40..0e041f2 100644 (file)
@@ -235,7 +235,7 @@ class MultiView(View):
                
                """
                clear_url_caches()
-               subpath = request.node.subpath
+               subpath = request.node._subpath
                view, args, kwargs = resolve(subpath, urlconf=self)
                view_args = getargspec(view)
                if extra_context is not None and ('extra_context' in view_args[0] or view_args[2] is not None):
diff --git a/philo/utils/lazycompat.py b/philo/utils/lazycompat.py
new file mode 100644 (file)
index 0000000..3876562
--- /dev/null
@@ -0,0 +1,97 @@
+try:
+       from django.utils.functional import empty, LazyObject, SimpleLazyObject
+except ImportError:
+       # Supply LazyObject and SimpleLazyObject for django < r16308
+       import operator
+       
+       
+       empty = object()
+       def new_method_proxy(func):
+               def inner(self, *args):
+                       if self._wrapped is empty:
+                               self._setup()
+                       return func(self._wrapped, *args)
+               return inner
+
+       class LazyObject(object):
+               """
+               A wrapper for another class that can be used to delay instantiation of the
+               wrapped class.
+
+               By subclassing, you have the opportunity to intercept and alter the
+               instantiation. If you don't need to do that, use SimpleLazyObject.
+               """
+               def __init__(self):
+                       self._wrapped = empty
+
+               __getattr__ = new_method_proxy(getattr)
+
+               def __setattr__(self, name, value):
+                       if name == "_wrapped":
+                               # Assign to __dict__ to avoid infinite __setattr__ loops.
+                               self.__dict__["_wrapped"] = value
+                       else:
+                               if self._wrapped is empty:
+                                       self._setup()
+                               setattr(self._wrapped, name, value)
+
+               def __delattr__(self, name):
+                       if name == "_wrapped":
+                               raise TypeError("can't delete _wrapped.")
+                       if self._wrapped is empty:
+                               self._setup()
+                       delattr(self._wrapped, name)
+
+               def _setup(self):
+                       """
+                       Must be implemented by subclasses to initialise the wrapped object.
+                       """
+                       raise NotImplementedError
+
+               # introspection support:
+               __members__ = property(lambda self: self.__dir__())
+               __dir__ = new_method_proxy(dir)
+
+
+       class SimpleLazyObject(LazyObject):
+               """
+               A lazy object initialised from any function.
+
+               Designed for compound objects of unknown type. For builtins or objects of
+               known type, use django.utils.functional.lazy.
+               """
+               def __init__(self, func):
+                       """
+                       Pass in a callable that returns the object to be wrapped.
+
+                       If copies are made of the resulting SimpleLazyObject, which can happen
+                       in various circumstances within Django, then you must ensure that the
+                       callable can be safely run more than once and will return the same
+                       value.
+                       """
+                       self.__dict__['_setupfunc'] = func
+                       super(SimpleLazyObject, self).__init__()
+
+               def _setup(self):
+                       self._wrapped = self._setupfunc()
+
+               __str__ = new_method_proxy(str)
+               __unicode__ = new_method_proxy(unicode)
+
+               def __deepcopy__(self, memo):
+                       if self._wrapped is empty:
+                               # We have to use SimpleLazyObject, not self.__class__, because the
+                               # latter is proxied.
+                               result = SimpleLazyObject(self._setupfunc)
+                               memo[id(self)] = result
+                               return result
+                       else:
+                               import copy
+                               return copy.deepcopy(self._wrapped, memo)
+
+               # Need to pretend to be the wrapped class, for the sake of objects that care
+               # about this (especially in equality tests)
+               __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
+               __eq__ = new_method_proxy(operator.eq)
+               __hash__ = new_method_proxy(hash)
+               __nonzero__ = new_method_proxy(bool)
\ No newline at end of file
index d3054b9..2c2a952 100644 (file)
@@ -37,10 +37,10 @@ def node_view(request, path=None, **kwargs):
                raise Http404
        
        node = request.node
-       subpath = request.node.subpath
+       subpath = request.node._subpath
        
        # Explicitly disallow trailing slashes if we are otherwise at a node's url.
-       if request._cached_node_path != "/" and request._cached_node_path[-1] == "/" and subpath == "/":
+       if node._path != "/" and node._path[-1] == "/" and subpath == "/":
                return HttpResponseRedirect(node.get_absolute_url())
        
        if not node.handles_subpath(subpath):