From 2380185d894e5a62a20b91a0c5a3a48497fb3cfb Mon Sep 17 00:00:00 2001 From: Joseph Spiros Date: Wed, 14 Jul 2010 21:37:13 -0400 Subject: [PATCH] Initial implementation of node_url templatetag. --- contrib/penfield/models.py | 18 +++++++++ exceptions.py | 7 ++++ models/base.py | 26 ++++++++++--- models/nodes.py | 14 ++++++- models/pages.py | 2 +- templatetags/nodes.py | 76 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 exceptions.py create mode 100644 templatetags/nodes.py diff --git a/contrib/penfield/models.py b/contrib/penfield/models.py index 0d8a374..7da8e08 100644 --- a/contrib/penfield/models.py +++ b/contrib/penfield/models.py @@ -1,7 +1,9 @@ from django.db import models from django.conf import settings from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model +from philo.exceptions import ViewCanNotProvideSubpath from django.conf.urls.defaults import url, patterns +from django.core.urlresolvers import reverse from django.http import Http404, HttpResponse from datetime import datetime from philo.contrib.penfield.utils import paginate @@ -59,6 +61,22 @@ class BlogView(MultiView): def __unicode__(self): return u'BlogView for %s' % self.blog.title + def get_subpath(self, obj): + if isinstance(obj, BlogEntry): + if obj.blog == self.blog: + entry_view_args = {'slug': obj.slug} + if self.entry_permalink_style in 'DMY': + entry_view_args.update({'year': str(obj.date.year).zfill(4)}) + if self.entry_permalink_style in 'DM': + entry_view_args.update({'month': str(obj.date.month).zfill(2)}) + if self.entry_permalink_style == 'D': + entry_view_args.update({'day': str(obj.date.day).zfill(2)}) + return reverse(self.entry_view, urlconf=self, kwargs=entry_view_args) + elif isinstance(obj, Tag): + if obj in self.blog.entry_tags: + return reverse(self.tag_view, urlconf=self, kwargs={'tag_slugs': obj.slug}) + raise ViewCanNotProvideSubpath + @property def urlpatterns(self): base_patterns = patterns('', diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 0000000..b40f08a --- /dev/null +++ b/exceptions.py @@ -0,0 +1,7 @@ +class ViewDoesNotProvideSubpaths(Exception): + """ Raised by get_subpath when the View does not provide subpaths (the default). """ + silent_variable_failure = True + +class ViewCanNotProvideSubpath(Exception): + """ Raised by get_subpath when the View can not provide a subpath for the supplied object. """ + silent_variable_failure = True \ No newline at end of file diff --git a/models/base.py b/models/base.py index 2f5bf93..6f23191 100644 --- a/models/base.py +++ b/models/base.py @@ -159,13 +159,29 @@ class TreeModel(models.Model): parent = models.ForeignKey('self', related_name='children', null=True, blank=True) slug = models.SlugField() - def get_path(self, pathsep='/', field='slug'): - path = getattr(self, field, '?') - parent = self.parent + def has_ancestor(self, ancestor): + parent = self while parent: - path = getattr(parent, field, '?') + pathsep + path + if parent == ancestor: + return True parent = parent.parent - return path + return False + + def get_path(self, root=None, pathsep='/', field='slug'): + if root is not None and self.has_ancestor(root): + path = '' + parent = self + while parent and parent != root: + path = getattr(parent, field, '?') + pathsep + path + parent = parent.parent + return path + else: + path = getattr(self, field, '?') + parent = self.parent + while parent and parent != root: + path = getattr(parent, field, '?') + pathsep + path + parent = parent.parent + return path path = property(get_path) def __unicode__(self): diff --git a/models/nodes.py b/models/nodes.py index 1e7622a..f46ddce 100644 --- a/models/nodes.py +++ b/models/nodes.py @@ -5,10 +5,12 @@ from django.contrib.sites.models import Site from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect from django.core.servers.basehttp import FileWrapper from django.core.urlresolvers import resolve +from django.template import add_to_builtins as register_templatetags from inspect import getargspec from philo.models.base import TreeEntity, Entity, QuerySetMapper from philo.utils import ContentTypeSubclassLimiter from philo.validators import RedirectValidator +from philo.exceptions import ViewDoesNotProvideSubpaths _view_content_type_limiter = ContentTypeSubclassLimiter(None) @@ -21,7 +23,9 @@ class Node(TreeEntity): @property def accepts_subpath(self): - return self.view.accepts_subpath + if self.view: + return self.view.accepts_subpath + return False def render_to_response(self, request, path=None, subpath=None, extra_context=None): return self.view.render_to_response(self, request, path, subpath, extra_context) @@ -39,6 +43,9 @@ class View(Entity): accepts_subpath = False + def get_subpath(self, obj): + raise ViewDoesNotProvideSubpaths + def attributes_with_node(self, node): return QuerySetMapper(self.attribute_set, passthrough=node.attributes) @@ -111,4 +118,7 @@ class File(View): app_label = 'philo' def __unicode__(self): - return self.file.name \ No newline at end of file + return self.file.name + + +register_templatetags('philo.templatetags.nodes') \ No newline at end of file diff --git a/models/pages.py b/models/pages.py index 5f75494..ff8e876 100644 --- a/models/pages.py +++ b/models/pages.py @@ -101,7 +101,7 @@ class Page(View): def render_to_response(self, node, request, path=None, subpath=None, extra_context=None): context = {} context.update(extra_context or {}) - context.update({'page': self, 'attributes': self.attributes_with_node(node), 'relationships': self.relationships_with_node(node)}) + context.update({'node': node, 'page': self, 'attributes': self.attributes_with_node(node), 'relationships': self.relationships_with_node(node)}) return HttpResponse(self.template.django_template.render(RequestContext(request, context)), mimetype=self.template.mimetype) def __unicode__(self): diff --git a/templatetags/nodes.py b/templatetags/nodes.py new file mode 100644 index 0000000..3be8194 --- /dev/null +++ b/templatetags/nodes.py @@ -0,0 +1,76 @@ +from django import template +from django.conf import settings +from django.contrib.sites.models import Site + + +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) + else: + self.node = None + + if with_obj is not None: + self.with_obj = template.Variable(with_obj) + else: + self.with_obj = None + + self.as_var = as_var + + def render(self, context): + 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) + url += node.view.get_subpath(with_obj) + else: + return settings.TEMPLATE_STRING_IF_INVALID + + 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 + + +@register.tag(name='node_url') +def do_node_url(parser, token): + """ + {% node_url [] [with ] [as ] %} + """ + params = token.split_contents() + tag = params[0] + + 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 -- 2.20.1