diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index 28c0195a48..dcf2d1ae0d 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -269,6 +269,7 @@ class MultiPartParser(object): """Cleanup filename from Internet Explorer full paths.""" return filename and filename[filename.rfind("\\")+1:].strip() + class LazyStream(six.Iterator): """ The LazyStream wrapper allows one to get and "unget" bytes from a stream. @@ -380,6 +381,7 @@ class LazyStream(six.Iterator): " if there is none, report this to the Django developers." ) + class ChunkIter(six.Iterator): """ An iterable that will yield chunks of data. Given a file-like object as the @@ -403,6 +405,7 @@ class ChunkIter(six.Iterator): def __iter__(self): return self + class InterBoundaryIter(six.Iterator): """ A Producer that will iterate over boundaries. @@ -420,6 +423,7 @@ class InterBoundaryIter(six.Iterator): except InputStreamExhausted: raise StopIteration() + class BoundaryIter(six.Iterator): """ A Producer that is sensitive to boundaries. @@ -516,6 +520,7 @@ class BoundaryIter(six.Iterator): end -= 1 return end, next + def exhaust(stream_or_iterable): """ Completely exhausts an iterator or stream. @@ -534,6 +539,7 @@ def exhaust(stream_or_iterable): for __ in iterator: pass + def parse_boundary_stream(stream, max_header_size): """ Parses one and exactly one stream that encapsulates a boundary. @@ -592,6 +598,7 @@ def parse_boundary_stream(stream, max_header_size): return (TYPE, outdict, stream) + class Parser(object): def __init__(self, stream, boundary): self._stream = stream @@ -603,6 +610,7 @@ class Parser(object): # Iterate over each part yield parse_boundary_stream(sub_stream, 1024) + def parse_header(line): """ Parse the header into a key-value. Input (line): bytes, output: unicode for key/name, bytes for value which @@ -622,6 +630,7 @@ def parse_header(line): pdict[name] = value return key, pdict + def _parse_header_params(s): plist = [] while s[:1] == b';': diff --git a/django/template/base.py b/django/template/base.py index ad13108e4e..efeca3ea2a 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -95,9 +95,11 @@ class VariableDoesNotExist(Exception): return self.msg % tuple(force_text(p, errors='replace') for p in self.params) + class InvalidTemplateLibrary(Exception): pass + class Origin(object): def __init__(self, name): self.name = name @@ -108,6 +110,7 @@ class Origin(object): def __str__(self): return self.name + class StringOrigin(Origin): def __init__(self, source): super(StringOrigin, self).__init__(UNKNOWN_SOURCE) @@ -116,6 +119,7 @@ class StringOrigin(Origin): def reload(self): return self.source + class Template(object): def __init__(self, template_string, origin=None, name=''): @@ -146,6 +150,7 @@ class Template(object): finally: context.render_context.pop() + def compile_string(template_string, origin): "Compiles template_string into NodeList ready for rendering" if settings.TEMPLATE_DEBUG: @@ -157,6 +162,7 @@ def compile_string(template_string, origin): parser = parser_class(lexer.tokenize()) return parser.parse() + class Token(object): def __init__(self, token_type, contents): # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or @@ -184,6 +190,7 @@ class Token(object): split.append(bit) return split + class Lexer(object): def __init__(self, template_string, origin): self.template_string = template_string @@ -235,6 +242,7 @@ class Lexer(object): self.lineno += token_string.count('\n') return token + class Parser(object): def __init__(self, tokens): self.tokens = tokens @@ -370,6 +378,7 @@ class Parser(object): else: raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) + class TokenParser(object): """ Subclass this and implement the top() method to parse a template line. @@ -523,6 +532,7 @@ filter_raw_string = r""" filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE) + class FilterExpression(object): """ Parses a variable token and its optional filters (all as a single string), @@ -644,6 +654,7 @@ class FilterExpression(object): def __str__(self): return self.token + def resolve_variable(path, context): """ Returns the resolved variable, which may contain attribute syntax, within @@ -653,6 +664,7 @@ def resolve_variable(path, context): """ return Variable(path).resolve(context) + class Variable(object): """ A template variable, resolvable against a given context. The variable may @@ -793,6 +805,7 @@ class Variable(object): return current + class Node(object): # Set this to True for nodes that must be first in the template (although # they can be preceded by text nodes. @@ -822,6 +835,7 @@ class Node(object): nodes.extend(nodelist.get_nodes_by_type(nodetype)) return nodes + class NodeList(list): # Set to True the first time a non-TextNode is inserted by # extend_nodelist(). @@ -847,6 +861,7 @@ class NodeList(list): def render_node(self, node, context): return node.render(context) + class TextNode(Node): def __init__(self, s): self.s = s @@ -858,6 +873,7 @@ class TextNode(Node): def render(self, context): return self.s + def render_value_in_context(value, context): """ Converts any value to a string to become part of a rendered template. This @@ -873,6 +889,7 @@ def render_value_in_context(value, context): else: return value + class VariableNode(Node): def __init__(self, filter_expression): self.filter_expression = filter_expression @@ -893,6 +910,7 @@ class VariableNode(Node): # Regex for token keyword arguments kwarg_re = re.compile(r"(?:(\w+)=)?(.+)") + def token_kwargs(bits, parser, support_legacy=False): """ A utility method for parsing token keyword arguments. @@ -942,6 +960,7 @@ def token_kwargs(bits, parser, support_legacy=False): del bits[:1] return kwargs + def parse_bits(parser, bits, params, varargs, varkw, defaults, takes_context, name): """ @@ -1009,6 +1028,7 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults, (name, ", ".join("'%s'" % p for p in unhandled_params))) return args, kwargs + def generic_tag_compiler(parser, token, params, varargs, varkw, defaults, name, takes_context, node_class): """ @@ -1019,6 +1039,7 @@ def generic_tag_compiler(parser, token, params, varargs, varkw, defaults, defaults, takes_context, name) return node_class(takes_context, args, kwargs) + class TagHelperNode(Node): """ Base class for tag helper nodes such as SimpleNode, InclusionNode and @@ -1039,6 +1060,7 @@ class TagHelperNode(Node): for k, v in self.kwargs.items()) return resolved_args, resolved_kwargs + class Library(object): def __init__(self): self.filters = {} @@ -1224,6 +1246,7 @@ class Library(object): return func return dec + def is_library_missing(name): """Check if library that failed to load cannot be found under any templatetags directory or does exist but fails to import. @@ -1240,6 +1263,7 @@ def is_library_missing(name): except ImportError: return is_library_missing(path) + def import_library(taglib_module): """ Load a template tag library module. @@ -1268,6 +1292,7 @@ def import_library(taglib_module): templatetags_modules = [] + def get_templatetags_modules(): """ Return the list of all available template tag modules. @@ -1290,6 +1315,7 @@ def get_templatetags_modules(): templatetags_modules = _templatetags_modules return templatetags_modules + def get_library(library_name): """ Load the template library module with the given name. diff --git a/django/template/context.py b/django/template/context.py index d16a0a7533..ba2c3d4d07 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -150,6 +150,7 @@ class RenderContext(BaseContext): return d[key] return otherwise + # This is a function rather than module-level procedural code because we only # want it to execute if somebody uses RequestContext. def get_standard_processors(): @@ -166,6 +167,7 @@ def get_standard_processors(): _standard_context_processors = tuple(processors) return _standard_context_processors + class RequestContext(Context): """ This subclass of template.Context automatically populates itself using diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index eb6f855cad..a4f0a89554 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -66,18 +66,21 @@ def addslashes(value): """ return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") + @register.filter(is_safe=True) @stringfilter def capfirst(value): """Capitalizes the first character of the value.""" return value and value[0].upper() + value[1:] + @register.filter("escapejs") @stringfilter def escapejs_filter(value): """Hex encodes characters for use in JavaScript strings.""" return escapejs(value) + @register.filter("fix_ampersands", is_safe=True) @stringfilter def fix_ampersands_filter(value): @@ -96,6 +99,7 @@ neg_inf = -1e200 * 1e200 nan = (1e200 * 1e200) // (1e200 * 1e200) special_floats = [str(pos_inf), str(neg_inf), str(nan)] + @register.filter(is_safe=True) def floatformat(text, arg=-1): """ @@ -179,12 +183,14 @@ def floatformat(text, arg=-1): except InvalidOperation: return input_val + @register.filter(is_safe=True) @stringfilter def iriencode(value): """Escapes an IRI value for use in a URL.""" return force_text(iri_to_uri(value)) + @register.filter(is_safe=True, needs_autoescape=True) @stringfilter def linenumbers(value, autoescape=None): @@ -201,12 +207,14 @@ def linenumbers(value, autoescape=None): lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) return mark_safe('\n'.join(lines)) + @register.filter(is_safe=True) @stringfilter def lower(value): """Converts a string into all lowercase.""" return value.lower() + @register.filter(is_safe=False) @stringfilter def make_list(value): @@ -218,6 +226,7 @@ def make_list(value): """ return list(value) + @register.filter(is_safe=True) @stringfilter def slugify(value): @@ -229,6 +238,7 @@ def slugify(value): from django.utils.text import slugify return slugify(value) + @register.filter(is_safe=True) def stringformat(value, arg): """ @@ -245,6 +255,7 @@ def stringformat(value, arg): except (ValueError, TypeError): return "" + @register.filter(is_safe=True) @stringfilter def title(value): @@ -252,6 +263,7 @@ def title(value): t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t) + @register.filter(is_safe=True) @stringfilter def truncatechars(value, arg): @@ -266,6 +278,7 @@ def truncatechars(value, arg): return value # Fail silently. return Truncator(value).chars(length) + @register.filter(is_safe=True) @stringfilter def truncatewords(value, arg): @@ -282,6 +295,7 @@ def truncatewords(value, arg): return value # Fail silently. return Truncator(value).words(length, truncate=' ...') + @register.filter(is_safe=True) @stringfilter def truncatewords_html(value, arg): @@ -298,12 +312,14 @@ def truncatewords_html(value, arg): return value # Fail silently. return Truncator(value).words(length, html=True, truncate=' ...') + @register.filter(is_safe=False) @stringfilter def upper(value): """Converts a string into all uppercase.""" return value.upper() + @register.filter(is_safe=False) @stringfilter def urlencode(value, safe=None): @@ -320,12 +336,14 @@ def urlencode(value, safe=None): kwargs['safe'] = safe return urlquote(value, **kwargs) + @register.filter(is_safe=True, needs_autoescape=True) @stringfilter def urlize(value, autoescape=None): """Converts URLs in plain text into clickable links.""" return mark_safe(urlize_impl(value, nofollow=True, autoescape=autoescape)) + @register.filter(is_safe=True, needs_autoescape=True) @stringfilter def urlizetrunc(value, limit, autoescape=None): @@ -338,12 +356,14 @@ def urlizetrunc(value, limit, autoescape=None): return mark_safe(urlize_impl(value, trim_url_limit=int(limit), nofollow=True, autoescape=autoescape)) + @register.filter(is_safe=False) @stringfilter def wordcount(value): """Returns the number of words.""" return len(value.split()) + @register.filter(is_safe=True) @stringfilter def wordwrap(value, arg): @@ -354,6 +374,7 @@ def wordwrap(value, arg): """ return wrap(value, int(arg)) + @register.filter(is_safe=True) @stringfilter def ljust(value, arg): @@ -364,6 +385,7 @@ def ljust(value, arg): """ return value.ljust(int(arg)) + @register.filter(is_safe=True) @stringfilter def rjust(value, arg): @@ -374,12 +396,14 @@ def rjust(value, arg): """ return value.rjust(int(arg)) + @register.filter(is_safe=True) @stringfilter def center(value, arg): """Centers the value in a field of a given width.""" return value.center(int(arg)) + @register.filter @stringfilter def cut(value, arg): @@ -392,6 +416,7 @@ def cut(value, arg): return mark_safe(value) return value + ################### # HTML STRINGS # ################### @@ -404,6 +429,7 @@ def escape_filter(value): """ return mark_for_escaping(value) + @register.filter(is_safe=True) @stringfilter def force_escape(value): @@ -414,6 +440,7 @@ def force_escape(value): """ return escape(value) + @register.filter("linebreaks", is_safe=True, needs_autoescape=True) @stringfilter def linebreaks_filter(value, autoescape=None): @@ -425,6 +452,7 @@ def linebreaks_filter(value, autoescape=None): autoescape = autoescape and not isinstance(value, SafeData) return mark_safe(linebreaks(value, autoescape)) + @register.filter(is_safe=True, needs_autoescape=True) @stringfilter def linebreaksbr(value, autoescape=None): @@ -438,6 +466,7 @@ def linebreaksbr(value, autoescape=None): value = escape(value) return mark_safe(value.replace('\n', '
')) + @register.filter(is_safe=True) @stringfilter def safe(value): @@ -446,6 +475,7 @@ def safe(value): """ return mark_safe(value) + @register.filter(is_safe=True) def safeseq(value): """ @@ -455,6 +485,7 @@ def safeseq(value): """ return [mark_safe(force_text(obj)) for obj in value] + @register.filter(is_safe=True) @stringfilter def removetags(value, tags): @@ -462,12 +493,14 @@ def removetags(value, tags): from django.utils.html import remove_tags return remove_tags(value, tags) + @register.filter(is_safe=True) @stringfilter def striptags(value): """Strips all [X]HTML tags.""" return strip_tags(value) + ################### # LISTS # ################### @@ -483,6 +516,7 @@ def dictsort(value, arg): except (TypeError, VariableDoesNotExist): return '' + @register.filter(is_safe=False) def dictsortreversed(value, arg): """ @@ -494,6 +528,7 @@ def dictsortreversed(value, arg): except (TypeError, VariableDoesNotExist): return '' + @register.filter(is_safe=False) def first(value): """Returns the first item in a list.""" @@ -502,6 +537,7 @@ def first(value): except IndexError: return '' + @register.filter(is_safe=True, needs_autoescape=True) def join(value, arg, autoescape=None): """ @@ -516,6 +552,7 @@ def join(value, arg, autoescape=None): return value return mark_safe(data) + @register.filter(is_safe=True) def last(value): "Returns the last item in a list" @@ -524,6 +561,7 @@ def last(value): except IndexError: return '' + @register.filter(is_safe=True) def length(value): """Returns the length of the value - useful for lists.""" @@ -532,6 +570,7 @@ def length(value): except (ValueError, TypeError): return '' + @register.filter(is_safe=False) def length_is(value, arg): """Returns a boolean of whether the value's length is the argument.""" @@ -540,11 +579,13 @@ def length_is(value, arg): except (ValueError, TypeError): return '' + @register.filter(is_safe=True) def random(value): """Returns a random item from the list.""" return random_module.choice(value) + @register.filter("slice", is_safe=True) def slice_filter(value, arg): """ @@ -566,6 +607,7 @@ def slice_filter(value, arg): except (ValueError, TypeError): return value # Fail silently. + @register.filter(is_safe=True, needs_autoescape=True) def unordered_list(value, autoescape=None): """ @@ -655,6 +697,7 @@ def unordered_list(value, autoescape=None): value, converted = convert_old_style_list(value) return mark_safe(_helper(value)) + ################### # INTEGERS # ################### @@ -670,6 +713,7 @@ def add(value, arg): except Exception: return '' + @register.filter(is_safe=False) def get_digit(value, arg): """ @@ -690,6 +734,7 @@ def get_digit(value, arg): except IndexError: return 0 + ################### # DATES # ################### @@ -709,6 +754,7 @@ def date(value, arg=None): except AttributeError: return '' + @register.filter(expects_localtime=True, is_safe=False) def time(value, arg=None): """Formats a time according to the given format.""" @@ -724,6 +770,7 @@ def time(value, arg=None): except AttributeError: return '' + @register.filter("timesince", is_safe=False) def timesince_filter(value, arg=None): """Formats a date as the time since that date (i.e. "4 days, 6 hours").""" @@ -736,6 +783,7 @@ def timesince_filter(value, arg=None): except (ValueError, TypeError): return '' + @register.filter("timeuntil", is_safe=False) def timeuntil_filter(value, arg=None): """Formats a date as the time until that date (i.e. "4 days, 6 hours").""" @@ -746,6 +794,7 @@ def timeuntil_filter(value, arg=None): except (ValueError, TypeError): return '' + ################### # LOGIC # ################### @@ -755,6 +804,7 @@ def default(value, arg): """If value is unavailable, use given default.""" return value or arg + @register.filter(is_safe=False) def default_if_none(value, arg): """If value is None, use given default.""" @@ -762,11 +812,13 @@ def default_if_none(value, arg): return arg return value + @register.filter(is_safe=False) def divisibleby(value, arg): """Returns True if the value is devisible by the argument.""" return int(value) % int(arg) == 0 + @register.filter(is_safe=False) def yesno(value, arg=None): """ @@ -799,6 +851,7 @@ def yesno(value, arg=None): return yes return no + ################### # MISC # ################### @@ -838,6 +891,7 @@ def filesizeformat(bytes): return avoid_wrapping(value) + @register.filter(is_safe=False) def pluralize(value, arg='s'): """ @@ -882,11 +936,13 @@ def pluralize(value, arg='s'): pass return singular_suffix + @register.filter("phone2numeric", is_safe=True) def phone2numeric_filter(value): """Takes a phone number and converts it in to its numerical equivalent.""" return phone2numeric(value) + @register.filter(is_safe=True) def pprint(value): """A wrapper around pprint.pprint -- for debugging, really.""" diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 2822c3d362..77d63ab256 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -25,6 +25,7 @@ from django.utils import timezone register = Library() + class AutoEscapeControlNode(Node): """Implements the actions of the autoescape tag.""" def __init__(self, setting, nodelist): @@ -40,10 +41,12 @@ class AutoEscapeControlNode(Node): else: return output + class CommentNode(Node): def render(self, context): return '' + class CsrfTokenNode(Node): def render(self, context): csrf_token = context.get('csrf_token', None) @@ -59,6 +62,7 @@ class CsrfTokenNode(Node): warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") return '' + class CycleNode(Node): def __init__(self, cyclevars, variable_name=None, silent=False, escape=False): self.cyclevars = cyclevars @@ -80,6 +84,7 @@ class CycleNode(Node): value = mark_safe(value) return render_value_in_context(value, context) + class DebugNode(Node): def render(self, context): from pprint import pformat @@ -88,6 +93,7 @@ class DebugNode(Node): output.append(pformat(sys.modules)) return ''.join(output) + class FilterNode(Node): def __init__(self, filter_expr, nodelist): self.filter_expr, self.nodelist = filter_expr, nodelist @@ -113,6 +119,7 @@ class FirstOfNode(Node): return render_value_in_context(value, context) return '' + class ForNode(Node): child_nodelists = ('nodelist_loop', 'nodelist_empty') @@ -207,6 +214,7 @@ class ForNode(Node): context.pop() return mark_safe(''.join(force_text(n) for n in nodelist)) + class IfChangedNode(Node): child_nodelists = ('nodelist_true', 'nodelist_false') @@ -251,6 +259,7 @@ class IfChangedNode(Node): # Using ifchanged outside loops. Effectively this is a no-op because the state is associated with 'self'. return context.render_context + class IfEqualNode(Node): child_nodelists = ('nodelist_true', 'nodelist_false') @@ -269,6 +278,7 @@ class IfEqualNode(Node): return self.nodelist_true.render(context) return self.nodelist_false.render(context) + class IfNode(Node): def __init__(self, conditions_nodelists): @@ -302,6 +312,7 @@ class IfNode(Node): return '' + class RegroupNode(Node): def __init__(self, target, expression, var_name): self.target, self.expression = target, expression @@ -328,6 +339,7 @@ class RegroupNode(Node): ] return '' + def include_is_allowed(filepath): filepath = os.path.abspath(filepath) for root in settings.ALLOWED_INCLUDE_ROOTS: @@ -335,6 +347,7 @@ def include_is_allowed(filepath): return True return False + class SsiNode(Node): def __init__(self, filepath, parsed): self.filepath = filepath @@ -364,10 +377,12 @@ class SsiNode(Node): return '' # Fail silently for invalid included templates. return output + class LoadNode(Node): def render(self, context): return '' + class NowNode(Node): def __init__(self, format_string): self.format_string = format_string @@ -376,6 +391,7 @@ class NowNode(Node): tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None return date(datetime.now(tz=tzinfo), self.format_string) + class SpacelessNode(Node): def __init__(self, nodelist): self.nodelist = nodelist @@ -384,6 +400,7 @@ class SpacelessNode(Node): from django.utils.html import strip_spaces_between_tags return strip_spaces_between_tags(self.nodelist.render(context).strip()) + class TemplateTagNode(Node): mapping = {'openblock': BLOCK_TAG_START, 'closeblock': BLOCK_TAG_END, @@ -401,6 +418,7 @@ class TemplateTagNode(Node): def render(self, context): return self.mapping.get(self.tagtype, '') + class URLNode(Node): def __init__(self, view_name, args, kwargs, asvar): self.view_name = view_name @@ -451,6 +469,7 @@ class URLNode(Node): else: return url + class VerbatimNode(Node): def __init__(self, content): self.content = content @@ -458,6 +477,7 @@ class VerbatimNode(Node): def render(self, context): return self.content + class WidthRatioNode(Node): def __init__(self, val_expr, max_expr, max_width, asvar=None): self.val_expr = val_expr @@ -490,6 +510,7 @@ class WidthRatioNode(Node): else: return result + class WithNode(Node): def __init__(self, var, name, nodelist, extra_context=None): self.nodelist = nodelist @@ -525,6 +546,7 @@ def autoescape(parser, token): parser.delete_first_token() return AutoEscapeControlNode((arg == 'on'), nodelist) + @register.tag def comment(parser, token): """ @@ -533,6 +555,7 @@ def comment(parser, token): parser.skip_past('endcomment') return CommentNode() + @register.tag def cycle(parser, token, escape=False): """ @@ -629,10 +652,12 @@ def cycle(parser, token, escape=False): node = CycleNode(values, escape=escape) return node + @register.tag def csrf_token(parser, token): return CsrfTokenNode() + @register.tag def debug(parser, token): """ @@ -647,6 +672,7 @@ def debug(parser, token): """ return DebugNode() + @register.tag('filter') def do_filter(parser, token): """ @@ -676,6 +702,7 @@ def do_filter(parser, token): parser.delete_first_token() return FilterNode(filter_expr, nodelist) + @register.tag def firstof(parser, token, escape=False): """ @@ -723,6 +750,7 @@ def firstof(parser, token, escape=False): raise TemplateSyntaxError("'firstof' statement requires at least one argument") return FirstOfNode([parser.compile_filter(bit) for bit in bits], escape=escape) + @register.tag('for') def do_for(parser, token): """ @@ -814,6 +842,7 @@ def do_for(parser, token): nodelist_empty = None return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) + def do_ifequal(parser, token, negate): bits = list(token.split_contents()) if len(bits) != 3: @@ -830,6 +859,7 @@ def do_ifequal(parser, token, negate): val2 = parser.compile_filter(bits[2]) return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) + @register.tag def ifequal(parser, token): """ @@ -849,6 +879,7 @@ def ifequal(parser, token): """ return do_ifequal(parser, token, False) + @register.tag def ifnotequal(parser, token): """ @@ -857,6 +888,7 @@ def ifnotequal(parser, token): """ return do_ifequal(parser, token, True) + class TemplateLiteral(Literal): def __init__(self, value, text): self.value = value @@ -868,6 +900,7 @@ class TemplateLiteral(Literal): def eval(self, context): return self.value.resolve(context, ignore_failures=True) + class TemplateIfParser(IfParser): error_class = TemplateSyntaxError @@ -878,6 +911,7 @@ class TemplateIfParser(IfParser): def create_var(self, value): return TemplateLiteral(self.template_parser.compile_filter(value), value) + @register.tag('if') def do_if(parser, token): """ @@ -1006,6 +1040,7 @@ def ifchanged(parser, token): values = [parser.compile_filter(bit) for bit in bits[1:]] return IfChangedNode(nodelist_true, nodelist_false, *values) + @register.tag def ssi(parser, token): """ @@ -1036,6 +1071,7 @@ def ssi(parser, token): filepath = parser.compile_filter(bits[1]) return SsiNode(filepath, parsed) + @register.tag def load(parser, token): """ @@ -1086,6 +1122,7 @@ def load(parser, token): (taglib, e)) return LoadNode() + @register.tag def now(parser, token): """ @@ -1104,6 +1141,7 @@ def now(parser, token): format_string = bits[1][1:-1] return NowNode(format_string) + @register.tag def regroup(parser, token): """ @@ -1172,6 +1210,7 @@ def regroup(parser, token): bits[3]) return RegroupNode(target, expression, var_name) + @register.tag def spaceless(parser, token): """ @@ -1202,6 +1241,7 @@ def spaceless(parser, token): parser.delete_first_token() return SpacelessNode(nodelist) + @register.tag def templatetag(parser, token): """ @@ -1236,6 +1276,7 @@ def templatetag(parser, token): (tag, list(TemplateTagNode.mapping))) return TemplateTagNode(tag) + @register.tag def url(parser, token): """ @@ -1331,6 +1372,7 @@ def url(parser, token): return URLNode(viewname, args, kwargs, asvar) + @register.tag def verbatim(parser, token): """ @@ -1353,6 +1395,7 @@ def verbatim(parser, token): parser.delete_first_token() return VerbatimNode(nodelist.render(Context())) + @register.tag def widthratio(parser, token): """ @@ -1390,6 +1433,7 @@ def widthratio(parser, token): parser.compile_filter(max_width), asvar=asvar) + @register.tag('with') def do_with(parser, token): """ diff --git a/django/template/loader.py b/django/template/loader.py index 410b03726a..3465a7141e 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -33,6 +33,7 @@ from django.utils import six template_source_loaders = None + class BaseLoader(object): is_usable = False @@ -71,6 +72,7 @@ class BaseLoader(object): """ pass + class LoaderOrigin(Origin): def __init__(self, display_name, loader, name, dirs): super(LoaderOrigin, self).__init__(display_name) @@ -79,12 +81,14 @@ class LoaderOrigin(Origin): def reload(self): return self.loader(self.loadname, self.dirs)[0] + def make_origin(display_name, loader, name, dirs): if settings.TEMPLATE_DEBUG and display_name: return LoaderOrigin(display_name, loader, name, dirs) else: return None + def find_template_loader(loader): if isinstance(loader, (tuple, list)): loader, args = loader[0], loader[1:] @@ -110,6 +114,7 @@ def find_template_loader(loader): else: raise ImproperlyConfigured('Loader does not define a "load_template" callable template source loader') + def find_template(name, dirs=None): # Calculate template_source_loaders the first time the function is executed # because putting this logic in the module-level namespace may cause @@ -130,6 +135,7 @@ def find_template(name, dirs=None): pass raise TemplateDoesNotExist(name) + def get_template(template_name, dirs=None): """ Returns a compiled Template object for the given template name, @@ -141,6 +147,7 @@ def get_template(template_name, dirs=None): template = get_template_from_string(template, origin, template_name) return template + def get_template_from_string(source, origin=None, name=None): """ Returns a compiled Template object for the given template code, @@ -148,6 +155,7 @@ def get_template_from_string(source, origin=None, name=None): """ return Template(source, origin, name) + def render_to_string(template_name, dictionary=None, context_instance=None, dirs=None): """ @@ -168,6 +176,7 @@ def render_to_string(template_name, dictionary=None, context_instance=None, with context_instance.push(dictionary): return t.render(context_instance) + def select_template(template_name_list, dirs=None): "Given a list of template names, returns the first that can be loaded." if not template_name_list: diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index c6bb3acadf..740b503cc5 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -11,9 +11,11 @@ register = Library() BLOCK_CONTEXT_KEY = 'block_context' + class ExtendsError(Exception): pass + class BlockContext(object): def __init__(self): # Dictionary of FIFO queues. @@ -38,6 +40,7 @@ class BlockContext(object): except IndexError: return None + class BlockNode(Node): def __init__(self, name, nodelist, parent=None): self.name, self.nodelist, self.parent = name, nodelist, parent @@ -71,6 +74,7 @@ class BlockNode(Node): return mark_safe(self.render(self.context)) return '' + class ExtendsNode(Node): must_be_first = True @@ -121,6 +125,7 @@ class ExtendsNode(Node): # the same. return compiled_parent._render(context) + class IncludeNode(Node): def __init__(self, template, *args, **kwargs): self.template = template @@ -177,6 +182,7 @@ def do_block(parser, token): return BlockNode(block_name, nodelist) + @register.tag('extends') def do_extends(parser, token): """ @@ -197,6 +203,7 @@ def do_extends(parser, token): raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0]) return ExtendsNode(nodelist, parent_name) + @register.tag('include') def do_include(parser, token): """ diff --git a/django/template/loaders/app_directories.py b/django/template/loaders/app_directories.py index 44983065e4..4f8ddfccac 100644 --- a/django/template/loaders/app_directories.py +++ b/django/template/loaders/app_directories.py @@ -32,6 +32,7 @@ for app in settings.INSTALLED_APPS: # It won't change, so convert it to a tuple to save memory. app_template_dirs = tuple(app_template_dirs) + class Loader(BaseLoader): is_usable = True diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py index 67cec49c74..646168cd1e 100644 --- a/django/template/loaders/cached.py +++ b/django/template/loaders/cached.py @@ -8,6 +8,7 @@ from django.template.base import TemplateDoesNotExist from django.template.loader import BaseLoader, get_template_from_string, find_template_loader, make_origin from django.utils.encoding import force_bytes + class Loader(BaseLoader): is_usable = True diff --git a/django/template/loaders/eggs.py b/django/template/loaders/eggs.py index 7da180323a..04e4f73b7b 100644 --- a/django/template/loaders/eggs.py +++ b/django/template/loaders/eggs.py @@ -11,6 +11,7 @@ from django.template.base import TemplateDoesNotExist from django.template.loader import BaseLoader from django.utils import six + class Loader(BaseLoader): is_usable = resource_string is not None diff --git a/django/template/loaders/filesystem.py b/django/template/loaders/filesystem.py index 1a7f0d2cd7..52e41ef0b9 100644 --- a/django/template/loaders/filesystem.py +++ b/django/template/loaders/filesystem.py @@ -7,6 +7,7 @@ from django.template.base import TemplateDoesNotExist from django.template.loader import BaseLoader from django.utils._os import safe_join + class Loader(BaseLoader): is_usable = True diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index fc8029d738..7c54ce034c 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -195,6 +195,7 @@ def do_get_available_languages(parser, token): raise TemplateSyntaxError("'get_available_languages' requires 'as variable' (got %r)" % args) return GetAvailableLanguagesNode(args[2]) + @register.tag("get_language_info") def do_get_language_info(parser, token): """ @@ -214,6 +215,7 @@ def do_get_language_info(parser, token): raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:])) return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4]) + @register.tag("get_language_info_list") def do_get_language_info_list(parser, token): """ @@ -237,18 +239,22 @@ def do_get_language_info_list(parser, token): raise TemplateSyntaxError("'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:])) return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4]) + @register.filter def language_name(lang_code): return translation.get_language_info(lang_code)['name'] + @register.filter def language_name_local(lang_code): return translation.get_language_info(lang_code)['name_local'] + @register.filter def language_bidi(lang_code): return translation.get_language_info(lang_code)['bidi'] + @register.tag("get_current_language") def do_get_current_language(parser, token): """ @@ -268,6 +274,7 @@ def do_get_current_language(parser, token): raise TemplateSyntaxError("'get_current_language' requires 'as variable' (got %r)" % args) return GetCurrentLanguageNode(args[2]) + @register.tag("get_current_language_bidi") def do_get_current_language_bidi(parser, token): """ @@ -287,6 +294,7 @@ def do_get_current_language_bidi(parser, token): raise TemplateSyntaxError("'get_current_language_bidi' requires 'as variable' (got %r)" % args) return GetCurrentLanguageBidiNode(args[2]) + @register.tag("trans") def do_translate(parser, token): """ @@ -366,6 +374,7 @@ def do_translate(parser, token): return TranslateNode(parser.compile_filter(value), noop, asvar, message_context) + @register.tag("blocktrans") def do_block_translate(parser, token): """ @@ -467,6 +476,7 @@ def do_block_translate(parser, token): return BlockTranslateNode(extra_context, singular, plural, countervar, counter, message_context) + @register.tag def language(parser, token): """ diff --git a/django/templatetags/l10n.py b/django/templatetags/l10n.py index 667de2470e..31dae9b105 100644 --- a/django/templatetags/l10n.py +++ b/django/templatetags/l10n.py @@ -5,6 +5,7 @@ from django.utils.encoding import force_text register = Library() + @register.filter(is_safe=False) def localize(value): """ @@ -13,6 +14,7 @@ def localize(value): """ return force_text(formats.localize(value, use_l10n=True)) + @register.filter(is_safe=False) def unlocalize(value): """ @@ -21,6 +23,7 @@ def unlocalize(value): """ return force_text(value) + class LocalizeNode(Node): def __init__(self, nodelist, use_l10n): self.nodelist = nodelist @@ -36,6 +39,7 @@ class LocalizeNode(Node): context.use_l10n = old_setting return output + @register.tag('localize') def localize_tag(parser, token): """ diff --git a/docs/_ext/applyxrefs.py b/docs/_ext/applyxrefs.py index cf5c3ac750..e9ba0fdd63 100644 --- a/docs/_ext/applyxrefs.py +++ b/docs/_ext/applyxrefs.py @@ -9,11 +9,13 @@ DONT_TOUCH = ( './index.txt', ) + def target_name(fn): if fn.endswith('.txt'): fn = fn[:-4] return '_' + fn.lstrip('./').replace('/', '-') + def process_file(fn, lines): lines.insert(0, '\n') lines.insert(0, '.. %s:\n' % target_name(fn)) @@ -23,6 +25,7 @@ def process_file(fn, lines): except IOError: print("Can't open %s for writing. Not touching it." % fn) + def has_target(fn): try: with open(fn, 'r') as fp: @@ -39,6 +42,7 @@ def has_target(fn): return (True, None) return (False, lines) + def main(argv=None): if argv is None: argv = sys.argv diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 523c86df52..6a9659171d 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -293,6 +293,7 @@ class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): SmartyPantsHTMLTranslator.visit_section(self, node) node['ids'] = old_ids + def parse_django_admin_node(env, sig, signode): command = sig.split(' ')[0] env._django_curr_admin_command = command @@ -300,6 +301,7 @@ def parse_django_admin_node(env, sig, signode): signode += addnodes.desc_name(title, title) return sig + def parse_django_adminopt_node(env, sig, signode): """A copy of sphinx.directives.CmdoptionDesc.parse_signature()""" from sphinx.domains.std import option_desc_re diff --git a/docs/_ext/literals_to_xrefs.py b/docs/_ext/literals_to_xrefs.py index 6c12ea43f3..268d084d42 100644 --- a/docs/_ext/literals_to_xrefs.py +++ b/docs/_ext/literals_to_xrefs.py @@ -37,6 +37,7 @@ ALWAYS_SKIP = [ "False", ] + def fixliterals(fname): with open(fname) as fp: data = fp.read() diff --git a/extras/csrf_migration_helper.py b/extras/csrf_migration_helper.py index ffa8b0b924..818c1263b7 100755 --- a/extras/csrf_migration_helper.py +++ b/extras/csrf_migration_helper.py @@ -137,6 +137,7 @@ _POST_FORM_RE = \ _FORM_CLOSE_RE = re.compile(r'') _TOKEN_RE = re.compile('\{% csrf_token') + def get_template_dirs(): """ Returns a set of all directories that contain project templates. @@ -153,6 +154,7 @@ def get_template_dirs(): dirs.update(app_template_dirs) return dirs + def make_template_info(filename, root_dirs): """ Creates a Template object for a filename, calculating the possible @@ -241,6 +243,7 @@ class Template(object): def __hash__(self): return hash(self.absolute_filename) + def get_templates(dirs): """ Returns all files in dirs that have template extensions, as Template @@ -257,6 +260,7 @@ def get_templates(dirs): templates.add(t) return templates + def get_python_code(paths): """ Returns all Python code, as a list of tuples, each one being: @@ -275,6 +279,7 @@ def get_python_code(paths): retval.append((fn, content)) return retval + def search_python_list(python_code, template_names): """ Searches python code for a list of template names. @@ -286,6 +291,7 @@ def search_python_list(python_code, template_names): retval.update(search_python(python_code, tn)) return sorted(retval) + def search_python(python_code, template_name): """ Searches Python code for a template name. @@ -300,6 +306,7 @@ def search_python(python_code, template_name): retval.append((fn, ln + 1)) return retval + def main(pythonpaths): template_dirs = get_template_dirs() templates = get_templates(template_dirs) diff --git a/scripts/manage_translations.py b/scripts/manage_translations.py index 5460eef25e..f256f7a1e7 100644 --- a/scripts/manage_translations.py +++ b/scripts/manage_translations.py @@ -27,6 +27,7 @@ from django.core.management import call_command HAVE_JS = ['admin'] + def _get_locale_dirs(include_core=True): """ Return a tuple (contrib name, absolute path) for all locale directories, @@ -44,6 +45,7 @@ def _get_locale_dirs(include_core=True): dirs.insert(0, ('core', os.path.join(os.getcwd(), 'django', 'conf', 'locale'))) return dirs + def _tx_resource_for_name(name): """ Return the Transifex resource name """ if name == 'core': @@ -51,6 +53,7 @@ def _tx_resource_for_name(name): else: return "django-core.contrib-%s" % name + def _check_diff(cat_name, base_path): """ Output the approximate number of changed/added strings in the en catalog.