From 872b260781ffcc72f81549bc92bde8ca4eb2155e Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Wed, 20 Oct 2010 14:45:58 -0400 Subject: [PATCH] Initial node_url_refactor commit. Revises node_url templatetag to accept args/kwargs like urlnode, using the current or given node's view as the urlconf. with is retained as a convenient shortcut. --- contrib/penfield/models.py | 44 ++++++------ models/nodes.py | 25 +++++-- templatetags/nodes.py | 141 ++++++++++++++++++++++--------------- 3 files changed, 127 insertions(+), 83 deletions(-) diff --git a/contrib/penfield/models.py b/contrib/penfield/models.py index c9c024c..0883811 100644 --- a/contrib/penfield/models.py +++ b/contrib/penfield/models.py @@ -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 diff --git a/models/nodes.py b/models/nodes.py index 14f5063..c75700c 100644 --- a/models/nodes.py +++ b/models/nodes.py @@ -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() diff --git a/templatetags/nodes.py b/templatetags/nodes.py index 0cb2289..6cee439 100644 --- a/templatetags/nodes.py +++ b/templatetags/nodes.py @@ -1,79 +1,110 @@ 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 [] [with ] [as ] %} + {% node_url [for ] [as [for ] [as ] %} + {% node_url [ [ ...] ] [for ] [as ] %} + {% node_url [= [= ...] ] [for ] [as ]%} """ 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 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 -- 2.20.1