from django.utils.text import capfirst
from philo.models.base import Entity
from philo.signals import entity_class_prepared
+from philo.validators import TemplateValidator
__all__ = ('AttributeField', 'RelationshipField')
def value_from_object(self, obj):
relobj = super(RelationshipField, self).value_from_object(obj)
- return getattr(relobj, 'pk', None)
\ No newline at end of file
+ return getattr(relobj, 'pk', None)
+
+
+class TemplateField(models.TextField):
+ def __init__(self, allow=None, disallow=None, *args, **kwargs):
+ super(TemplateField, self).__init__(*args, **kwargs)
+ self.validators.append(TemplateValidator(allow, disallow))
+
+
+try:
+ from south.modelsinspector import add_introspection_rules
+except ImportError:
+ pass
+else:
+ add_introspection_rules([], ["^philo\.models\.fields\.TemplateField"])
\ No newline at end of file
from django.template import TemplateDoesNotExist
from django.template import Context, RequestContext
from django.template.loader import get_template
-from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, IncludeNode
+from django.template.loader_tags import ExtendsNode, ConstantIncludeNode
from django.http import HttpResponse
from philo.models.base import TreeModel, register_value_model
from philo.models.nodes import View
from philo.utils import fattr
from philo.templatetags.containers import ContainerNode
+from philo.validators import LOADED_TEMPLATE_ATTR
from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
+BLANK_CONTEXT = Context()
+
+
+def get_extended(self):
+ return self.get_parent(BLANK_CONTEXT)
+
+
+def get_included(self):
+ return self.template
+
+
+# We ignore the IncludeNode because it will never work in a blank context.
+setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended))
+setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included))
+
+
class Template(TreeModel):
name = models.CharField(max_length=255)
documentation = models.TextField(null=True, blank=True)
for nodelist_name in node.child_nodelists:
if hasattr(node, nodelist_name):
nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
+
+ # _philo_additional_template is a property philo provides on all nodes that require it
+ # and which it monkeypatches onto the relevant default nodes.
+ if hasattr(node, LOADED_TEMPLATE_ATTR) and getattr(node, LOADED_TEMPLATE_ATTR):
+ nodes.extend(container_nodes(LOADED_TEMPLATE_ATTR))
+
if isinstance(node, ContainerNode):
nodes.append(node)
- elif isinstance(node, ExtendsNode):
- extended_template = node.get_parent(Context())
- if extended_template:
- nodes.extend(container_nodes(extended_template))
- elif isinstance(node, ConstantIncludeNode):
- included_template = node.template
- if included_template:
- nodes.extend(container_nodes(included_template))
- elif isinstance(node, IncludeNode):
- included_template = get_template(node.template_name.resolve(Context()))
- if included_template:
- nodes.extend(container_nodes(included_template))
except:
raise # fail for this node
return nodes
import re
+LOADED_TEMPLATE_ATTR = '_philo_loaded_template'
+INSECURE_TAGS = (
+ 'load',
+ 'extends',
+ 'include',
+)
+
+
class RedirectValidator(RegexValidator):
"""Based loosely on the URLValidator, but no option to verify_exists"""
regex = re.compile(
try:
json.loads(value)
except:
- raise ValidationError(u'\'%s\' is not valid JSON' % value)
\ No newline at end of file
+ raise ValidationError(u'\'%s\' is not valid JSON' % value)
+
+
+from django.template import Template, Parser, Lexer, TOKEN_BLOCK
+
+
+class TemplateValidationParser(Parser):
+ def __init__(self, tokens, allow=None, disallow=None, secure=True):
+ super(TemplateValidationParser, self).__init__(tokens)
+
+ allow, disallow = set(allow or []), set(disallow or [])
+
+ if secure:
+ disallow |= set(INSECURE_TAGS)
+
+ self.allow, self.disallow = allow, disallow
+
+ def parse(self, parse_until=None):
+ if parse_until is None:
+ parse_until = []
+
+ nodelist = self.create_nodelist()
+ while self.tokens:
+ token = self.next_token()
+ # We only need to parse var and block tokens.
+ if token.token_type == TOKEN_VAR:
+ if not token.contents:
+ self.empty_variable(token)
+
+ filter_expression = self.compile_filter(token.contents)
+ var_node = self.create_variable_node(filter_expression)
+ self.extend_nodelist(nodelist, var_node,token)
+ elif token.token_type == TOKEN_BLOCK:
+ if token.contents in parse_until:
+ # put token back on token list so calling code knows why it terminated
+ self.prepend_token(token)
+ return nodelist
+
+ try:
+ command = token.contents.split()[0]
+ except IndexError:
+ self.empty_block_tag(token)
+
+ if (self.allow and command not in self.allow) or (self.disallow and command in self.disallow):
+ self.disallowed_tag(command)
+
+ self.enter_command(command, token)
+
+ try:
+ compile_func = self.tags[command]
+ except KeyError:
+ self.invalid_block_tag(token, command, parse_until)
+
+ try:
+ compiled_result = compile_func(self, token)
+ except TemplateSyntaxError, e:
+ if not self.compile_function_error(token, e):
+ raise
+
+ self.extend_nodelist(nodelist, compiled_result, token)
+ self.exit_command()
+
+ if parse_until:
+ self.unclosed_block_tag(parse_until)
+
+ return nodelist
+
+ def disallowed_tag(self, command):
+ raise ValidationError("Tag not allowed: %s" % command)
+
+
+class TemplateValidator(object):
+ def __init__(self, allow=None, disallow=None, secure=True):
+ self.allow = allow
+ self.disallow = disallow
+ self.secure = secure
+
+ def __call__(self, value):
+ try:
+ self.validate_template(value)
+ except ValidationError:
+ raise
+ except Exception, e:
+ raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e))
+
+ def validate_template(self, template_string):
+ # We want to tokenize like normal, then use a custom parser.
+ lexer = Lexer(template_string, None)
+ tokens = lexer.tokenize()
+ parser = TemplateValidationParser(tokens, self.allow, self.disallow, self.secure)
+
+ for node in parser.parse():
+ template = getattr(node, LOADED_TEMPLATE_ATTR, None)
\ No newline at end of file