Skip to content

Commit

Permalink
Merge pull request #1 from drtyrsa/inclusion_tag
Browse files Browse the repository at this point in the history
Inclusion tag
  • Loading branch information
drtyrsa committed Nov 29, 2011
2 parents 1cdab0e + d068d1c commit e790458
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 130 deletions.
42 changes: 26 additions & 16 deletions src/easytags/library.py
Expand Up @@ -8,7 +8,7 @@


from django.template import Library from django.template import Library


from node import EasyNode, EasyAsNode from node import EasyNode, EasyAsNode, EasyIncNode


class EasyLibrary(Library): class EasyLibrary(Library):


Expand All @@ -18,40 +18,50 @@ def _get_name_and_renderer(cls, name, renderer):
renderer = name renderer = name
name = renderer.__name__ name = renderer.__name__
return name, renderer return name, renderer

def easytag(self, name = None, renderer = None): def easytag(self, name = None, renderer = None):
return self._handle_decorator(EasyNode, name, renderer) return self._handle_decorator(EasyNode, name, renderer)

def easyastag(self, name = None, renderer = None): def easyastag(self, name = None, renderer = None):
return self._handle_decorator(EasyAsNode, name, renderer) return self._handle_decorator(EasyAsNode, name, renderer)



def easyinctag(self, name = None, renderer = None, **kwargs):
def _handle_decorator(self, node_class, name, renderer): if 'template_name' not in kwargs:
raise TypeError('Named argument "template_name" is required')
return self._handle_decorator(EasyIncNode, name, renderer, **kwargs)

def _handle_decorator(self, node_class, name, renderer, **kwargs):
if not name and not renderer: if not name and not renderer:
return self.easytag return self.easytag
if not renderer: if not renderer:
if callable(name): if callable(name):
renderer = name renderer = name
return self._register_easytag(node_class, renderer.__name__, renderer) return self._register_easytag(node_class, renderer.__name__, renderer, **kwargs)
else: else:
def dec(renderer): def dec(renderer):
return self._register_easytag(node_class, name, renderer) return self._register_easytag(node_class, name, renderer, **kwargs)
return dec return dec
return self._register_easytag(node_class, name, renderer) return self._register_easytag(node_class, name, renderer, **kwargs)

def _register_easytag(self, node_class, name, renderer): def _register_easytag(self, node_class, name, renderer, **kwargs):
if not renderer: if not renderer:
renderer = name renderer = name
name = renderer.__name__ name = renderer.__name__

def render_context(self, context, *args, **kwargs): def render_context(self, context, *args, **kwargs):
return renderer(context, *args, **kwargs) return renderer(context, *args, **kwargs)

get_argspec = classmethod(lambda cls: node_class.get_argspec(renderer)) get_argspec = classmethod(lambda cls: node_class.get_argspec(renderer))

tag_node = type('%sEasyNode' % name, (node_class,), { class_dict = {
'render_context': render_context, 'render_context': render_context,
'get_argspec': get_argspec, 'get_argspec': get_argspec,
}) }

if 'template_name' in kwargs:
class_dict['template_name'] = kwargs['template_name']
class_dict['takes_context'] = kwargs.get('takes_context', False)

tag_node = type('%sEasyNode' % name, (node_class,), class_dict)
self.tag(name, tag_node.parse) self.tag(name, tag_node.parse)
return renderer return renderer
74 changes: 44 additions & 30 deletions src/easytags/node.py
Expand Up @@ -8,72 +8,78 @@


from inspect import getargspec from inspect import getargspec


from django.template import Node, Variable, TemplateSyntaxError from django.template import Node, Variable, TemplateSyntaxError, Context
from django.template.loader import render_to_string




is_kwarg = lambda bit: not bit[0] in (u'"', u"'") and u'=' in bit is_kwarg = lambda bit: not bit[0] in (u'"', u"'") and u'=' in bit




def get_args_kwargs_from_bits(bits): def get_args_kwargs_from_bits(parser, bits):
args = [] args = []
kwargs = {} kwargs = {}
for bit in bits: for bit in bits:
if is_kwarg(bit): if is_kwarg(bit):
splitted_bit = bit.split(u'=') key, value = bit.split(u'=', 1)
kwargs[splitted_bit[0]] = u'='.join(splitted_bit[1:]) kwargs[key] = parser.compile_filter(value)
else: else:
if not kwargs: if not kwargs:
args.append(bit) args.append(parser.compile_filter(bit))
else: else:
raise TemplateSyntaxError(u"Args must be before kwargs.") raise TemplateSyntaxError(u"Args must be before kwargs.")

return {'args': tuple(args), 'kwargs': kwargs} return {'args': tuple(args), 'kwargs': kwargs}


def SmartVariable(var):
if hasattr(var, 'resolve'):
return var
return Variable(var)



class EasyNode(Node): class EasyNode(Node):

@classmethod @classmethod
def parse_to_args_kwargs(cls, parser, token): def parse_to_args_kwargs(cls, parser, token):
bits = token.split_contents() bits = token.split_contents()
return get_args_kwargs_from_bits(bits[1:]) return get_args_kwargs_from_bits(parser, bits[1:])

@classmethod @classmethod
def parse(cls, parser, token): def parse(cls, parser, token):
args_kwargs = cls.parse_to_args_kwargs(parser, token) args_kwargs = cls.parse_to_args_kwargs(parser, token)
cls.is_args_kwargs_valid(args_kwargs) cls.is_args_kwargs_valid(args_kwargs)
return cls(args_kwargs) return cls(args_kwargs)

@classmethod @classmethod
def get_argspec(cls, func = None): def get_argspec(cls, func = None):
func = func or cls.render_context func = func or cls.render_context
return getargspec(func) return getargspec(func)

@classmethod @classmethod
def is_args_kwargs_valid(cls, args_kwargs): def is_args_kwargs_valid(cls, args_kwargs):
render_context_spec = cls.get_argspec() render_context_spec = cls.get_argspec()

args = args_kwargs['args'] args = args_kwargs['args']
kwargs = args_kwargs['kwargs'] kwargs = args_kwargs['kwargs']

valid_args_names = render_context_spec.args valid_args_names = render_context_spec.args
if 'self' in valid_args_names: valid_args_names.remove('self') if 'self' in valid_args_names: valid_args_names.remove('self')
if 'context' in valid_args_names: valid_args_names.remove('context') if 'context' in valid_args_names: valid_args_names.remove('context')

n_args_kwargs = len(args) + len(kwargs) n_args_kwargs = len(args) + len(kwargs)

max_n_args_kwargs = len(valid_args_names) max_n_args_kwargs = len(valid_args_names)
if not render_context_spec.varargs and not render_context_spec.keywords and n_args_kwargs > max_n_args_kwargs: if not render_context_spec.varargs and not render_context_spec.keywords and n_args_kwargs > max_n_args_kwargs:
raise TemplateSyntaxError(u'Invalid number of args %s (max. %s)' % (n_args_kwargs, max_n_args_kwargs)) raise TemplateSyntaxError(u'Invalid number of args %s (max. %s)' % (n_args_kwargs, max_n_args_kwargs))

min_n_args_kwargs = max_n_args_kwargs - len(render_context_spec.defaults or ()) min_n_args_kwargs = max_n_args_kwargs - len(render_context_spec.defaults or ())
if n_args_kwargs < min_n_args_kwargs: if n_args_kwargs < min_n_args_kwargs:
raise TemplateSyntaxError(u'Invalid number of args %s (min. %s)' % (n_args_kwargs, max_n_args_kwargs)) raise TemplateSyntaxError(u'Invalid number of args %s (min. %s)' % (n_args_kwargs, max_n_args_kwargs))

required_args_names = valid_args_names[len(args):min_n_args_kwargs] required_args_names = valid_args_names[len(args):min_n_args_kwargs]
for required_arg_name in required_args_names: for required_arg_name in required_args_names:
if not required_arg_name in kwargs: if not required_arg_name in kwargs:
raise TemplateSyntaxError(u'Required arg missing: %s' % required_arg_name) raise TemplateSyntaxError(u'Required arg missing: %s' % required_arg_name)

first_kwarg_index = len(args) first_kwarg_index = len(args)
if not render_context_spec.keywords: if not render_context_spec.keywords:
valid_kwargs = valid_args_names[first_kwarg_index:] valid_kwargs = valid_args_names[first_kwarg_index:]
Expand All @@ -84,23 +90,23 @@ def is_args_kwargs_valid(cls, args_kwargs):
defined_args = valid_args_names[:first_kwarg_index] defined_args = valid_args_names[:first_kwarg_index]
for kwarg in kwargs: for kwarg in kwargs:
if kwarg in defined_args: if kwarg in defined_args:
raise TemplateSyntaxError(u'%s was defined twice.' % kwarg) raise TemplateSyntaxError(u'%s was defined twice.' % kwarg)

def __init__(self, args_kwargs): def __init__(self, args_kwargs):
self.args = [Variable(arg) for arg in args_kwargs['args']] self.args = [SmartVariable(arg) for arg in args_kwargs['args']]
self.kwargs = dict((key, Variable(value)) for key, value in args_kwargs['kwargs'].items()) self.kwargs = dict((key, SmartVariable(value)) for key, value in args_kwargs['kwargs'].iteritems())

def render(self, context): def render(self, context):
args = [arg.resolve(context) for arg in self.args] args = [arg.resolve(context) for arg in self.args]
kwargs = dict((str(key), value.resolve(context)) for key, value in self.kwargs.items()) kwargs = dict((str(key), value.resolve(context)) for key, value in self.kwargs.iteritems())
return self.render_context(context, *args, **kwargs) return self.render_context(context, *args, **kwargs)

def render_context(self, context, *args, **kwargs): def render_context(self, context, *args, **kwargs):
raise NotImplementedError raise NotImplementedError




class EasyAsNode(EasyNode): class EasyAsNode(EasyNode):

@classmethod @classmethod
def parse_to_args_kwargs(cls, parser, token): def parse_to_args_kwargs(cls, parser, token):
bits = token.split_contents()[1:] bits = token.split_contents()[1:]
Expand All @@ -109,17 +115,25 @@ def parse_to_args_kwargs(cls, parser, token):
bits = bits[:-2] bits = bits[:-2]
else: else:
varname = None varname = None
args_kwargs = get_args_kwargs_from_bits(bits) args_kwargs = get_args_kwargs_from_bits(parser, bits)
args_kwargs['varname'] = varname args_kwargs['varname'] = varname
return args_kwargs return args_kwargs

def __init__(self, args_kwargs): def __init__(self, args_kwargs):
super(EasyAsNode, self).__init__(args_kwargs) super(EasyAsNode, self).__init__(args_kwargs)
self.varname = args_kwargs['varname'] self.varname = args_kwargs['varname']

def render(self, context): def render(self, context):
rendered = super(EasyAsNode, self).render(context) rendered = super(EasyAsNode, self).render(context)
if self.varname: if self.varname:
context[self.varname] = rendered context[self.varname] = rendered
return u'' return u''
return rendered return rendered

class EasyIncNode(EasyNode):
def render(self, context):
if not self.takes_context:
context = Context({})
rendered = super(EasyIncNode, self).render(context)
context.update(rendered)
return render_to_string(self.template_name, context)

0 comments on commit e790458

Please sign in to comment.