diff --git a/django/template/__init__.py b/django/template/__init__.py index 4c386be..f16f739 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -49,10 +49,12 @@ u'

Hello

' u'' """ import re +import imp from inspect import getargspec from django.conf import settings from django.template.context import Context, RequestContext, ContextPopException +from django.templatetags import get_templatetags_modules from django.utils.importlib import import_module from django.utils.itercompat import is_iterable from django.utils.functional import curry, Promise @@ -965,23 +967,47 @@ class Library(object): self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) return func return dec - -def get_library(module_name): - lib = libraries.get(module_name, None) +def import_library(taglib_module): + components = taglib_module.split('.') + parent_components = components[:-1] + parent_module = '.'.join(parent_components) + try: + mod = __import__(parent_module, {}, {}, [parent_components[-1]]) + imp.find_module(components[-1], mod.__path__) + except ImportError: + return None + mod = __import__(taglib_module, {}, {}, [components[-1]]) + try: + return mod.register + except AttributeError: + raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module) + +def get_library(library_name): + lib = libraries.get(library_name, None) if not lib: - try: - mod = import_module(module_name) - except ImportError, e: - raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e)) - try: - lib = mod.register - libraries[module_name] = lib - except AttributeError: - raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) + """ + If library is not already loaded loop over all templatetags modules to locate it. + + {% load somelib %} and {% load someotherlib %} loops twice. + + Subsequent loads eg. {% load somelib %} in the same thread will grab the cached + module from libraries. + """ + templatetags_modules = get_templatetags_modules() + tried_modules = [] + for module in templatetags_modules: + taglib_module = str('%s.%s' % (module, library_name)) + tried_modules.append(taglib_module) + lib = import_library(taglib_module) + if lib: + libraries[library_name] = lib + break + if not lib: + raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules))) return lib -def add_to_builtins(module_name): - builtins.append(get_library(module_name)) +def add_to_builtins(module): + builtins.append(import_library(module)) -add_to_builtins('django.template.defaulttags') -add_to_builtins('django.template.defaultfilters') +add_to_builtins('django.templatetags.defaulttags') +add_to_builtins('django.templatetags.defaultfilters') diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py deleted file mode 100644 index 0ba16bc..0000000 --- a/django/template/defaultfilters.py +++ /dev/null @@ -1,929 +0,0 @@ -"""Default variable filters.""" - -import re - -try: - from decimal import Decimal, InvalidOperation, ROUND_HALF_UP -except ImportError: - from django.utils._decimal import Decimal, InvalidOperation, ROUND_HALF_UP - -import random as random_module -try: - from functools import wraps -except ImportError: - from django.utils.functional import wraps # Python 2.3, 2.4 fallback. - -from django.template import Variable, Library -from django.conf import settings -from django.utils import formats -from django.utils.translation import ugettext, ungettext -from django.utils.encoding import force_unicode, iri_to_uri -from django.utils.safestring import mark_safe, SafeData - -register = Library() - -####################### -# STRING DECORATOR # -####################### - -def stringfilter(func): - """ - Decorator for filters which should only receive unicode objects. The object - passed as the first positional argument will be converted to a unicode - object. - """ - def _dec(*args, **kwargs): - if args: - args = list(args) - args[0] = force_unicode(args[0]) - if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False): - return mark_safe(func(*args, **kwargs)) - return func(*args, **kwargs) - - # Include a reference to the real function (used to check original - # arguments by the template parser). - _dec._decorated_function = getattr(func, '_decorated_function', func) - for attr in ('is_safe', 'needs_autoescape'): - if hasattr(func, attr): - setattr(_dec, attr, getattr(func, attr)) - return wraps(func)(_dec) - -################### -# STRINGS # -################### - -def addslashes(value): - """ - Adds slashes before quotes. Useful for escaping strings in CSV, for - example. Less useful for escaping JavaScript; use the ``escapejs`` - filter instead. - """ - return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") -addslashes.is_safe = True -addslashes = stringfilter(addslashes) - -def capfirst(value): - """Capitalizes the first character of the value.""" - return value and value[0].upper() + value[1:] -capfirst.is_safe=True -capfirst = stringfilter(capfirst) - -_base_js_escapes = ( - ('\\', r'\x5C'), - ('\'', r'\x27'), - ('"', r'\x22'), - ('>', r'\x3E'), - ('<', r'\x3C'), - ('&', r'\x26'), - ('=', r'\x3D'), - ('-', r'\x2D'), - (';', r'\x3B'), - (u'\u2028', r'\u2028'), - (u'\u2029', r'\u2029') -) - -# Escape every ASCII character with a value less than 32. -_js_escapes = (_base_js_escapes + - tuple([('%c' % z, '\\x%02X' % z) for z in range(32)])) - -def escapejs(value): - """Hex encodes characters for use in JavaScript strings.""" - for bad, good in _js_escapes: - value = value.replace(bad, good) - return value -escapejs = stringfilter(escapejs) - -def fix_ampersands(value): - """Replaces ampersands with ``&`` entities.""" - from django.utils.html import fix_ampersands - return fix_ampersands(value) -fix_ampersands.is_safe=True -fix_ampersands = stringfilter(fix_ampersands) - -# Values for testing floatformat input against infinity and NaN representations, -# which differ across platforms and Python versions. Some (i.e. old Windows -# ones) are not recognized by Decimal but we want to return them unchanged vs. -# returning an empty string as we do for completley invalid input. Note these -# need to be built up from values that are not inf/nan, since inf/nan values do -# not reload properly from .pyc files on Windows prior to some level of Python 2.5 -# (see Python Issue757815 and Issue1080440). -pos_inf = 1e200 * 1e200 -neg_inf = -1e200 * 1e200 -nan = (1e200 * 1e200) / (1e200 * 1e200) -special_floats = [str(pos_inf), str(neg_inf), str(nan)] - -def floatformat(text, arg=-1): - """ - Displays a float to a specified number of decimal places. - - If called without an argument, it displays the floating point number with - one decimal place -- but only if there's a decimal place to be displayed: - - * num1 = 34.23234 - * num2 = 34.00000 - * num3 = 34.26000 - * {{ num1|floatformat }} displays "34.2" - * {{ num2|floatformat }} displays "34" - * {{ num3|floatformat }} displays "34.3" - - If arg is positive, it will always display exactly arg number of decimal - places: - - * {{ num1|floatformat:3 }} displays "34.232" - * {{ num2|floatformat:3 }} displays "34.000" - * {{ num3|floatformat:3 }} displays "34.260" - - If arg is negative, it will display arg number of decimal places -- but - only if there are places to be displayed: - - * {{ num1|floatformat:"-3" }} displays "34.232" - * {{ num2|floatformat:"-3" }} displays "34" - * {{ num3|floatformat:"-3" }} displays "34.260" - - If the input float is infinity or NaN, the (platform-dependent) string - representation of that value will be displayed. - """ - - try: - input_val = force_unicode(text) - d = Decimal(input_val) - except UnicodeEncodeError: - return u'' - except InvalidOperation: - if input_val in special_floats: - return input_val - try: - d = Decimal(force_unicode(float(text))) - except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError): - return u'' - try: - p = int(arg) - except ValueError: - return input_val - - try: - m = int(d) - d - except (ValueError, OverflowError, InvalidOperation): - return input_val - - if not m and p < 0: - return mark_safe(formats.number_format(u'%d' % (int(d)), 0)) - - if p == 0: - exp = Decimal(1) - else: - exp = Decimal('1.0') / (Decimal(10) ** abs(p)) - try: - return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p))) - except InvalidOperation: - return input_val -floatformat.is_safe = True - -def iriencode(value): - """Escapes an IRI value for use in a URL.""" - return force_unicode(iri_to_uri(value)) -iriencode.is_safe = True -iriencode = stringfilter(iriencode) - -def linenumbers(value, autoescape=None): - """Displays text with line numbers.""" - from django.utils.html import escape - lines = value.split(u'\n') - # Find the maximum width of the line count, for use with zero padding - # string format command - width = unicode(len(unicode(len(lines)))) - if not autoescape or isinstance(value, SafeData): - for i, line in enumerate(lines): - lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line) - else: - for i, line in enumerate(lines): - lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) - return mark_safe(u'\n'.join(lines)) -linenumbers.is_safe = True -linenumbers.needs_autoescape = True -linenumbers = stringfilter(linenumbers) - -def lower(value): - """Converts a string into all lowercase.""" - return value.lower() -lower.is_safe = True -lower = stringfilter(lower) - -def make_list(value): - """ - Returns the value turned into a list. - - For an integer, it's a list of digits. - For a string, it's a list of characters. - """ - return list(value) -make_list.is_safe = False -make_list = stringfilter(make_list) - -def slugify(value): - """ - Normalizes string, converts to lowercase, removes non-alpha characters, - and converts spaces to hyphens. - """ - import unicodedata - value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') - value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) - return mark_safe(re.sub('[-\s]+', '-', value)) -slugify.is_safe = True -slugify = stringfilter(slugify) - -def stringformat(value, arg): - """ - Formats the variable according to the arg, a string formatting specifier. - - This specifier uses Python string formating syntax, with the exception that - the leading "%" is dropped. - - See http://docs.python.org/lib/typesseq-strings.html for documentation - of Python string formatting - """ - try: - return (u"%" + unicode(arg)) % value - except (ValueError, TypeError): - return u"" -stringformat.is_safe = True - -def title(value): - """Converts a string into titlecase.""" - 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) -title.is_safe = True -title = stringfilter(title) - -def truncatewords(value, arg): - """ - Truncates a string after a certain number of words. - - Argument: Number of words to truncate after. - """ - from django.utils.text import truncate_words - try: - length = int(arg) - except ValueError: # Invalid literal for int(). - return value # Fail silently. - return truncate_words(value, length) -truncatewords.is_safe = True -truncatewords = stringfilter(truncatewords) - -def truncatewords_html(value, arg): - """ - Truncates HTML after a certain number of words. - - Argument: Number of words to truncate after. - """ - from django.utils.text import truncate_html_words - try: - length = int(arg) - except ValueError: # invalid literal for int() - return value # Fail silently. - return truncate_html_words(value, length) -truncatewords_html.is_safe = True -truncatewords_html = stringfilter(truncatewords_html) - -def upper(value): - """Converts a string into all uppercase.""" - return value.upper() -upper.is_safe = False -upper = stringfilter(upper) - -def urlencode(value): - """Escapes a value for use in a URL.""" - from django.utils.http import urlquote - return urlquote(value) -urlencode.is_safe = False -urlencode = stringfilter(urlencode) - -def urlize(value, autoescape=None): - """Converts URLs in plain text into clickable links.""" - from django.utils.html import urlize - return mark_safe(urlize(value, nofollow=True, autoescape=autoescape)) -urlize.is_safe=True -urlize.needs_autoescape = True -urlize = stringfilter(urlize) - -def urlizetrunc(value, limit, autoescape=None): - """ - Converts URLs into clickable links, truncating URLs to the given character - limit, and adding 'rel=nofollow' attribute to discourage spamming. - - Argument: Length to truncate URLs to. - """ - from django.utils.html import urlize - return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True, - autoescape=autoescape)) -urlizetrunc.is_safe = True -urlizetrunc.needs_autoescape = True -urlizetrunc = stringfilter(urlizetrunc) - -def wordcount(value): - """Returns the number of words.""" - return len(value.split()) -wordcount.is_safe = False -wordcount = stringfilter(wordcount) - -def wordwrap(value, arg): - """ - Wraps words at specified line length. - - Argument: number of characters to wrap the text at. - """ - from django.utils.text import wrap - return wrap(value, int(arg)) -wordwrap.is_safe = True -wordwrap = stringfilter(wordwrap) - -def ljust(value, arg): - """ - Left-aligns the value in a field of a given width. - - Argument: field size. - """ - return value.ljust(int(arg)) -ljust.is_safe = True -ljust = stringfilter(ljust) - -def rjust(value, arg): - """ - Right-aligns the value in a field of a given width. - - Argument: field size. - """ - return value.rjust(int(arg)) -rjust.is_safe = True -rjust = stringfilter(rjust) - -def center(value, arg): - """Centers the value in a field of a given width.""" - return value.center(int(arg)) -center.is_safe = True -center = stringfilter(center) - -def cut(value, arg): - """ - Removes all values of arg from the given string. - """ - safe = isinstance(value, SafeData) - value = value.replace(arg, u'') - if safe and arg != ';': - return mark_safe(value) - return value -cut = stringfilter(cut) - -################### -# HTML STRINGS # -################### - -def escape(value): - """ - Marks the value as a string that should not be auto-escaped. - """ - from django.utils.safestring import mark_for_escaping - return mark_for_escaping(value) -escape.is_safe = True -escape = stringfilter(escape) - -def force_escape(value): - """ - Escapes a string's HTML. This returns a new string containing the escaped - characters (as opposed to "escape", which marks the content for later - possible escaping). - """ - from django.utils.html import escape - return mark_safe(escape(value)) -force_escape = stringfilter(force_escape) -force_escape.is_safe = True - -def linebreaks(value, autoescape=None): - """ - Replaces line breaks in plain text with appropriate HTML; a single - newline becomes an HTML line break (``
``) and a new line - followed by a blank line becomes a paragraph break (``

``). - """ - from django.utils.html import linebreaks - autoescape = autoescape and not isinstance(value, SafeData) - return mark_safe(linebreaks(value, autoescape)) -linebreaks.is_safe = True -linebreaks.needs_autoescape = True -linebreaks = stringfilter(linebreaks) - -def linebreaksbr(value, autoescape=None): - """ - Converts all newlines in a piece of plain text to HTML line breaks - (``
``). - """ - if autoescape and not isinstance(value, SafeData): - from django.utils.html import escape - value = escape(value) - return mark_safe(value.replace('\n', '
')) -linebreaksbr.is_safe = True -linebreaksbr.needs_autoescape = True -linebreaksbr = stringfilter(linebreaksbr) - -def safe(value): - """ - Marks the value as a string that should not be auto-escaped. - """ - return mark_safe(value) -safe.is_safe = True -safe = stringfilter(safe) - -def safeseq(value): - """ - A "safe" filter for sequences. Marks each element in the sequence, - individually, as safe, after converting them to unicode. Returns a list - with the results. - """ - return [mark_safe(force_unicode(obj)) for obj in value] -safeseq.is_safe = True - -def removetags(value, tags): - """Removes a space separated list of [X]HTML tags from the output.""" - tags = [re.escape(tag) for tag in tags.split()] - tags_re = u'(%s)' % u'|'.join(tags) - starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) - endtag_re = re.compile(u'' % tags_re) - value = starttag_re.sub(u'', value) - value = endtag_re.sub(u'', value) - return value -removetags.is_safe = True -removetags = stringfilter(removetags) - -def striptags(value): - """Strips all [X]HTML tags.""" - from django.utils.html import strip_tags - return strip_tags(value) -striptags.is_safe = True -striptags = stringfilter(striptags) - -################### -# LISTS # -################### - -def dictsort(value, arg): - """ - Takes a list of dicts, returns that list sorted by the property given in - the argument. - """ - var_resolve = Variable(arg).resolve - decorated = [(var_resolve(item), item) for item in value] - decorated.sort() - return [item[1] for item in decorated] -dictsort.is_safe = False - -def dictsortreversed(value, arg): - """ - Takes a list of dicts, returns that list sorted in reverse order by the - property given in the argument. - """ - var_resolve = Variable(arg).resolve - decorated = [(var_resolve(item), item) for item in value] - decorated.sort() - decorated.reverse() - return [item[1] for item in decorated] -dictsortreversed.is_safe = False - -def first(value): - """Returns the first item in a list.""" - try: - return value[0] - except IndexError: - return u'' -first.is_safe = False - -def join(value, arg, autoescape=None): - """ - Joins a list with a string, like Python's ``str.join(list)``. - """ - value = map(force_unicode, value) - if autoescape: - from django.utils.html import conditional_escape - value = [conditional_escape(v) for v in value] - try: - data = arg.join(value) - except AttributeError: # fail silently but nicely - return value - return mark_safe(data) -join.is_safe = True -join.needs_autoescape = True - -def last(value): - "Returns the last item in a list" - try: - return value[-1] - except IndexError: - return u'' -last.is_safe = True - -def length(value): - """Returns the length of the value - useful for lists.""" - try: - return len(value) - except (ValueError, TypeError): - return '' -length.is_safe = True - -def length_is(value, arg): - """Returns a boolean of whether the value's length is the argument.""" - try: - return len(value) == int(arg) - except (ValueError, TypeError): - return '' -length_is.is_safe = False - -def random(value): - """Returns a random item from the list.""" - return random_module.choice(value) -random.is_safe = True - -def slice_(value, arg): - """ - Returns a slice of the list. - - Uses the same syntax as Python's list slicing; see - http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice - for an introduction. - """ - try: - bits = [] - for x in arg.split(u':'): - if len(x) == 0: - bits.append(None) - else: - bits.append(int(x)) - return value[slice(*bits)] - - except (ValueError, TypeError): - return value # Fail silently. -slice_.is_safe = True - -def unordered_list(value, autoescape=None): - """ - Recursively takes a self-nested list and returns an HTML unordered list -- - WITHOUT opening and closing