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 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 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
 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 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}
                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)})
                                                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:
                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):
                raise ViewCanNotProvideSubpath
        
        def get_context(self):
@@ -310,7 +301,7 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
        def __unicode__(self):
                return self.newsletter.__unicode__()
        
        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}
                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)})
                                                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:
                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
                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.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.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
 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):
                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:
                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:
                        return None
        
        class Meta:
@@ -80,7 +89,13 @@ _view_content_type_limiter.cls = View
 class MultiView(View):
        accepts_subpath = True
        
 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()
        
        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 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):
 
 
 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:
                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:
                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:
                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):
        """
 
 
 @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]
        """
        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