Initial node_url_refactor commit. Revises node_url templatetag to accept args/kwargs...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Wed, 20 Oct 2010 18:45:58 +0000 (14:45 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Wed, 20 Oct 2010 18:45:58 +0000 (14:45 -0400)
contrib/penfield/models.py
models/nodes.py
templatetags/nodes.py

index c9c024c..0883811 100644 (file)
@@ -3,9 +3,8 @@ from django.conf import settings
 from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField
 from philo.exceptions import ViewCanNotProvideSubpath
 from django.conf.urls.defaults import url, patterns, include
-from django.core.urlresolvers import reverse
 from django.http import Http404
-from datetime import datetime
+from datetime import date, datetime
 from philo.utils import paginate
 from philo.contrib.penfield.validators import validate_pagination_count
 from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
@@ -75,7 +74,7 @@ class BlogView(MultiView, FeedMultiViewMixin):
        def per_page(self):
                return self.entries_per_page
        
-       def get_subpath(self, obj):
+       def get_reverse_params(self, obj):
                if isinstance(obj, BlogEntry):
                        if obj.blog == self.blog:
                                kwargs = {'slug': obj.slug}
@@ -85,25 +84,17 @@ class BlogView(MultiView, FeedMultiViewMixin):
                                                kwargs.update({'month': str(obj.date.month).zfill(2)})
                                                if self.entry_permalink_style == 'D':
                                                        kwargs.update({'day': str(obj.date.day).zfill(2)})
-                               return reverse(self.entry_view, urlconf=self, kwargs=kwargs)
+                               return self.entry_view, [], kwargs
                elif isinstance(obj, Tag):
                        if obj in self.blog.entry_tags:
-                               return reverse('entries_by_tag', urlconf=self, kwargs={'tag_slugs': obj.slug})
-               elif isinstance(obj, (str, unicode)):
-                       split_obj = obj.split(':')
-                       if len(split_obj) > 1:
-                               kwargs = {}
-                               try:
-                                       kwargs.update({'year': str(int(split_obj[1])).zfill(4)})
-                                       if len(split_obj) > 2:
-                                               kwargs.update({'month': str(int(split_obj[2])).zfill(2)})
-                                               if len(split_obj) > 3:
-                                                       kwargs.update({'day': str(int(split_obj[3])).zfill(2)})
-                                                       return reverse('entries_by_day', urlconf=self, kwargs=kwargs)
-                                               return reverse('entries_by_month', urlconf=self, kwargs=kwargs)
-                                       return reverse('entries_by_year', urlconf=self, kwargs=kwargs)
-                               except:
-                                       pass
+                               return 'entries_by_tag', [], {'tag_slugs': obj.slug}
+               elif isinstance(obj, (date, datetime)):
+                       kwargs = {
+                               'year': str(obj.year).zfill(4),
+                               'month': str(obj.month).zfill(2),
+                               'day': str(obj.day).zfill(2)
+                       }
+                       return 'entries_by_day', [], kwargs
                raise ViewCanNotProvideSubpath
        
        def get_context(self):
@@ -310,7 +301,7 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
        def __unicode__(self):
                return self.newsletter.__unicode__()
        
-       def get_subpath(self, obj):
+       def get_reverse_params(self, obj):
                if isinstance(obj, NewsletterArticle):
                        if obj.newsletter == self.newsletter:
                                kwargs = {'slug': obj.slug}
@@ -320,10 +311,17 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
                                                kwargs.update({'month': str(obj.date.month).zfill(2)})
                                                if self.article_permalink_style == 'D':
                                                        kwargs.update({'day': str(obj.date.day).zfill(2)})
-                               return reverse(self.article_view, urlconf=self, kwargs=kwargs)
+                               return self.article_view, [], kwargs
                elif isinstance(obj, NewsletterIssue):
                        if obj.newsletter == self.newsletter:
-                               return reverse('issue', urlconf=self, kwargs={'numbering': obj.numbering})
+                               return 'issue', [], {'numbering': obj.numbering}
+               elif isinstance(obj, (date, datetime)):
+                       kwargs = {
+                               'year': str(obj.year).zfill(4),
+                               'month': str(obj.month).zfill(2),
+                               'day': str(obj.day).zfill(2)
+                       }
+                       return 'articles_by_day', [], kwargs
                raise ViewCanNotProvideSubpath
        
        @property
index 14f5063..c75700c 100644 (file)
@@ -3,8 +3,9 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes import generic
 from django.contrib.sites.models import Site
 from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect
+from django.core.exceptions import ViewDoesNotExist
 from django.core.servers.basehttp import FileWrapper
-from django.core.urlresolvers import resolve, clear_url_caches
+from django.core.urlresolvers import resolve, clear_url_caches, reverse
 from django.template import add_to_builtins as register_templatetags
 from inspect import getargspec
 from philo.models.base import TreeEntity, Entity, QuerySetMapper, register_value_model
@@ -32,10 +33,18 @@ class Node(TreeEntity):
                return self.view.render_to_response(self, request, path, subpath, extra_context)
        
        def get_absolute_url(self):
-               root = Site.objects.get_current().root_node
                try:
-                       return '/%s' % self.get_path(root=root)
-               except AncestorDoesNotExist:
+                       root = Site.objects.get_current().root_node
+               except Site.DoesNotExist:
+                       root = None
+               
+               try:
+                       path = self.get_path(root=root)
+                       if path:
+                               path += '/'
+                       root_url = reverse('philo-root')
+                       return '%s%s' % (root_url, path)
+               except AncestorDoesNotExist, ViewDoesNotExist:
                        return None
        
        class Meta:
@@ -80,7 +89,13 @@ _view_content_type_limiter.cls = View
 class MultiView(View):
        accepts_subpath = True
        
-       urlpatterns = []
+       @property
+       def urlpatterns(self, obj):
+               raise NotImplementedError("MultiView subclasses must implement urlpatterns.")
+       
+       def get_reverse_params(self, obj):
+               """This method should return a view_name, args, kwargs tuple suitable for reversing a url for the given obj using self as the urlconf."""
+               raise NotImplementedError("MultiView subclasses must implement get_subpath.")
        
        def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
                clear_url_caches()
index 0cb2289..6cee439 100644 (file)
 from django import template
 from django.conf import settings
 from django.contrib.sites.models import Site
+from django.core.urlresolvers import reverse, NoReverseMatch
+from django.template.defaulttags import kwarg_re
+from philo.exceptions import ViewCanNotProvideSubpath
 
 
 register = template.Library()
 
 
 class NodeURLNode(template.Node):
-       def __init__(self, node, with_obj, as_var):
-               if node is not None:
-                       self.node = template.Variable(node)
+       def __init__(self, node, as_var, with_obj=None, view_name=None, args=None, kwargs=None):
+               self.as_var = as_var
+               self.view_name = view_name
+               
+               # Because the following variables have already been compiled as filters if they exist, they don't need to be re-scanned as template variables.
+               self.node = node
+               self.with_obj = with_obj
+               self.args = args
+               self.kwargs = kwargs
+       
+       def render(self, context):
+               if self.node:
+                       node = self.node.resolve(context)
                else:
-                       self.node = None
+                       node = context['node']
                
-               if with_obj is not None:
-                       self.with_obj = template.Variable(with_obj)
+               if self.with_obj is not None:
+                       try:
+                               view_name, args, kwargs = node.view.get_reverse_params(self.with_obj.resolve(context))
+                       except ViewCanNotProvideSubpath:
+                               return settings.TEMPLATE_STRING_IF_INVALID
+               elif self.view_name is not None:
+                       view_name = self.view_name
+                       args = [arg.resolve(context) for arg in self.args]
+                       kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) for k, v in self.kwargs.items()])
                else:
-                       self.with_obj = None
+                       return node.get_absolute_url()
                
-               self.as_var = as_var
-       
-       def render(self, context):
+               if not node.view.accepts_subpath:
+                       return settings.TEMPLATE_STRING_IF_INVALID
+               
+               url = ''
                try:
-                       if self.node:
-                               node = self.node.resolve(context)
-                       else:
-                               node = context['node']
-                       current_site = Site.objects.get_current()
-                       if node.has_ancestor(current_site.root_node):
-                               url = '/' + node.get_path(root=current_site.root_node)
-                               if self.with_obj:
-                                       with_obj = self.with_obj.resolve(context)
-                                       subpath = node.view.get_subpath(with_obj)
-                                       if subpath[0] is '/':
-                                               subpath = subpath[1:]
-                                       url += subpath
-                       else:
-                               return settings.TEMPLATE_STRING_IF_INVALID
+                       subpath = reverse(view_name, urlconf=node.view, args=args, kwargs=kwargs)
+               except NoReverseMatch:
+                       if self.as_var is None:
+                               raise
+               else:
+                       if subpath[0] == '/':
+                               subpath = subpath[1:]
                        
-                       if self.as_var:
-                               context[self.as_var] = url
-                               return settings.TEMPLATE_STRING_IF_INVALID
-                       else:
-                               return url
-               except:
-                       return settings.TEMPLATE_STRING_IF_INVALID
+                       url = node.get_absolute_url() + subpath
+               
+               if self.as_var:
+                       context[self.as_var] = url
+                       return ''
+               else:
+                       return url
 
 
 @register.tag(name='node_url')
 def do_node_url(parser, token):
        """
-       {% node_url [<node>] [with <obj>] [as <var>] %}
+       {% node_url [for <node>] [as <var] %}
+       {% node_url with <obj> [for <node>] [as <var>] %}
+       {% node_url <view_name> [<arg1> [<arg2> ...] ] [for <node>] [as <var>] %}
+       {% node_url <view_name> [<key1>=<value1> [<key2>=<value2> ...] ] [for <node>] [as <var>]%}
        """
        params = token.split_contents()
        tag = params[0]
+       as_var = None
+       with_obj = None
+       node = None
+       params = params[1:]
+       
+       if len(params) >= 2 and params[-2] == 'as':
+               as_var = params[-1]
+               params = params[:-2]
+       
+       if len(params) >= 2 and params[-2] == 'for':
+               node = parser.compile_filter(params[-1])
+               params = params[:-2]
+       
+       if len(params) >= 2 and params[-2] == 'with':
+               with_obj = parser.compile_filter(params[-1])
+               params = params[:-2]
+       
+       if with_obj is not None:
+               if params:
+                       raise template.TemplateSyntaxError('`%s` template tag accepts no arguments or keyword arguments if with <obj> is specified.' % tag)
+               return NodeURLNode(with_obj=with_obj, node=node, as_var=as_var)
+       
+       if params:
+               args = []
+               kwargs = {}
+               view_name = params.pop(0)
+               for param in params:
+                       match = kwarg_re.match(param)
+                       if not match:
+                               raise TemplateSyntaxError("Malformed arguments to `%s` tag" % tag)
+                       name, value = match.groups()
+                       if name:
+                               kwargs[name] = parser.compile_filter(value)
+                       else:
+                               args.append(parser.compile_filter(value))
+               return NodeURLNode(view_name=view_name, args=args, kwargs=kwargs, node=node, as_var=as_var)
        
-       if len(params) <= 6:
-               node = None
-               with_obj = None
-               as_var = None
-               remaining_tokens = params[1:]
-               while remaining_tokens:
-                       option_token = remaining_tokens.pop(0)
-                       if option_token == 'with':
-                               try:
-                                       with_obj = remaining_tokens.pop(0)
-                               except IndexError:
-                                       raise template.TemplateSyntaxError('"%s" template tag option "with" requires an argument specifying an object handled by the view on the node' % tag)
-                       elif option_token == 'as':
-                               try:
-                                       as_var = remaining_tokens.pop(0)
-                               except IndexError:
-                                       raise template.TemplateSyntaxError('"%s" template tag option "as" requires an argument specifying a variable name' % tag)
-                       else: # node
-                               node = option_token
-               return NodeURLNode(node=node, with_obj=with_obj, as_var=as_var)
-       else:
-               raise template.TemplateSyntaxError('"%s" template tag cannot accept more than five arguments' % tag)
\ No newline at end of file
+       return NodeURLNode(node=node, as_var=as_var)
\ No newline at end of file