diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index c76857e4b6..12d26dd4b7 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -97,9 +97,9 @@ TEMPLATE_FILE_EXTENSION = '.html'
# See the comments in django/core/template/loader.py for interface
# documentation.
TEMPLATE_LOADERS = (
- 'django.core.template.loaders.filesystem.load_template_source',
- 'django.core.template.loaders.app_directories.load_template_source',
-# 'django.core.template.loaders.eggs.load_template_source',
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
)
# List of processors used by DjangoContext to populate the context.
diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
index 511726a453..c5cd9ce17d 100644
--- a/django/conf/project_template/settings.py
+++ b/django/conf/project_template/settings.py
@@ -45,9 +45,9 @@ SECRET_KEY = ''
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
- 'django.core.template.loaders.filesystem.load_template_source',
- 'django.core.template.loaders.app_directories.load_template_source',
-# 'django.core.template.loaders.eggs.load_template_source',
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
)
MIDDLEWARE_CLASSES = (
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index d2f7a97557..280a1922c7 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -1,7 +1,7 @@
from django.contrib.admin.views.changelist import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR
from django.contrib.admin.views.changelist import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
from django.contrib.admin.views.changelist import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS
-from django.core import template
+from django import template
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import dateformat
@@ -9,7 +9,7 @@ from django.utils.html import strip_tags, escape
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
from django.conf.settings import ADMIN_MEDIA_PREFIX
-from django.core.template import Library
+from django.template import Library
register = Library()
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index 9afe134dc2..835e254c43 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -1,4 +1,5 @@
-from django.core import template, template_loader
+from django import template
+from djang.core import template_loader
from django.utils.html import escape
from django.utils.text import capfirst
from django.utils.functional import curry
diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py
index 97dbb1b5ae..007df5b765 100644
--- a/django/contrib/admin/templatetags/adminapplist.py
+++ b/django/contrib/admin/templatetags/adminapplist.py
@@ -1,4 +1,4 @@
-from django.core import template
+from django import template
register = template.Library()
diff --git a/django/contrib/admin/templatetags/adminmedia.py b/django/contrib/admin/templatetags/adminmedia.py
index 3238bfcfc7..bebed4a84f 100644
--- a/django/contrib/admin/templatetags/adminmedia.py
+++ b/django/contrib/admin/templatetags/adminmedia.py
@@ -1,4 +1,4 @@
-from django.core.template import Library
+from django.template import Library
register = Library()
def admin_media_prefix():
diff --git a/django/contrib/admin/templatetags/breadcrumbs.py b/django/contrib/admin/templatetags/breadcrumbs.py
index 41705bbdb4..a8a48f2ea2 100644
--- a/django/contrib/admin/templatetags/breadcrumbs.py
+++ b/django/contrib/admin/templatetags/breadcrumbs.py
@@ -1,4 +1,4 @@
-from django.core.template import Library
+from django.template import Library
register = Library()
diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py
index dc07ea44f5..e29ba40d18 100644
--- a/django/contrib/admin/templatetags/log.py
+++ b/django/contrib/admin/templatetags/log.py
@@ -1,5 +1,5 @@
from django.contrib.admin.models import LogEntry
-from django.core import template
+from django import template
register = template.Library()
diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py
index 34a945a60e..27eeace5ee 100644
--- a/django/contrib/admin/views/doc.py
+++ b/django/contrib/admin/views/doc.py
@@ -1,11 +1,11 @@
-from django import templatetags
+from django import template, templatetags
from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.db import models
from django.core.extensions import DjangoContext, render_to_response
from django.core.exceptions import ViewDoesNotExist
from django.http import Http404
-from django.core import template, urlresolvers
+from django.core import urlresolvers
from django.contrib.admin import utils
from django.contrib.sites.models import Site
import inspect, os, re
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index c1f12039cc..1b98b6ce42 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -1,8 +1,9 @@
# Generic admin views.
from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
-from django.core import formfields, template
-from django.core.template import loader
+from django.core import formfields
+from django import template
+from django.template import loader
from django.db import models
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
diff --git a/django/contrib/admin/views/stages/add.py b/django/contrib/admin/views/stages/add.py
index ffbfd67a0c..d019c117ca 100644
--- a/django/contrib/admin/views/stages/add.py
+++ b/django/contrib/admin/views/stages/add.py
@@ -2,7 +2,8 @@ from django.contrib.admin.models import LogEntry
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.admin.views.main import get_model_and_app
from django.contrib.admin.views.stages.modify import render_change_form
-from django.core import formfields, template
+from django.core import formfields
+from django import template
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
from django.core.extensions import DjangoContext as Context
diff --git a/django/contrib/admin/views/stages/change.py b/django/contrib/admin/views/stages/change.py
index 26d578eed5..fafc1d0315 100644
--- a/django/contrib/admin/views/stages/change.py
+++ b/django/contrib/admin/views/stages/change.py
@@ -1,5 +1,6 @@
from django.contrib.admin.views.main import get_model_and_app
-from django.core import formfields, template
+from django.core import formfields
+from django import template
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
from django.core.extensions import DjangoContext as Context
diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py
index 33f0e7662f..9b3824f99f 100644
--- a/django/contrib/admin/views/template.py
+++ b/django/contrib/admin/views/template.py
@@ -1,7 +1,7 @@
from django.contrib.admin.views.decorators import staff_member_required
from django.core import formfields, validators
-from django.core import template
-from django.core.template import loader
+from django import template
+from django.template import loader
from django.core.extensions import DjangoContext, render_to_response
from django.contrib.sites.models import Site
from django.conf import settings
diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py
index 9b9d79202b..5c2b1dfae0 100644
--- a/django/contrib/comments/templatetags/comments.py
+++ b/django/contrib/comments/templatetags/comments.py
@@ -1,7 +1,7 @@
from django.contrib.comments.models import Comment, FreeComment
from django.contrib.comments.models import PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
from django.contrib.comments.models import MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION
-from django.core import template
+from django import template
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType
import re
diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py
index 5124889b89..933d56e520 100644
--- a/django/contrib/markup/templatetags/markup.py
+++ b/django/contrib/markup/templatetags/markup.py
@@ -14,7 +14,7 @@ In each case, if the required library is not installed, the filter will
silently fail and return the un-marked-up text.
"""
-from django.core import template
+from django import template
register = template.Library()
diff --git a/django/contrib/syndication/feeds.py b/django/contrib/syndication/feeds.py
index 902cde7003..79328f8ef9 100644
--- a/django/contrib/syndication/feeds.py
+++ b/django/contrib/syndication/feeds.py
@@ -1,5 +1,5 @@
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
-from django.core.template import Context, loader, Template, TemplateDoesNotExist
+from django.template import Context, loader, Template, TemplateDoesNotExist
from django.contrib.sites.models import Site
from django.utils import feedgenerator
from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE
diff --git a/django/core/extensions.py b/django/core/extensions.py
index 62c1407a62..bd33b08db5 100644
--- a/django/core/extensions.py
+++ b/django/core/extensions.py
@@ -3,7 +3,7 @@
# for convenience's sake.
from django.core.exceptions import ImproperlyConfigured
-from django.core.template import Context, loader
+from django.template import Context, loader
from django.conf.settings import TEMPLATE_CONTEXT_PROCESSORS
from django.http import HttpResponse, Http404
diff --git a/django/core/template_loader.py b/django/core/template_loader.py
index e268c390e1..ee86178cc1 100644
--- a/django/core/template_loader.py
+++ b/django/core/template_loader.py
@@ -1,7 +1,7 @@
# This module is DEPRECATED!
#
-# You should no longer be using django.core.template_loader.
+# You should no longer be using django.template_loader.
#
-# Use django.core.template.loader instead.
+# Use django.template.loader instead.
-from django.core.template.loader import *
+from django.template.loader import *
diff --git a/django/template/__init__.py b/django/template/__init__.py
new file mode 100644
index 0000000000..929cea179b
--- /dev/null
+++ b/django/template/__init__.py
@@ -0,0 +1,914 @@
+"""
+This is the Django template system.
+
+How it works:
+
+The Lexer.tokenize() function converts a template string (i.e., a string containing
+markup with custom template tags) to tokens, which can be either plain text
+(TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
+
+The Parser() class takes a list of tokens in its constructor, and its parse()
+method returns a compiled template -- which is, under the hood, a list of
+Node objects.
+
+Each Node is responsible for creating some sort of output -- e.g. simple text
+(TextNode), variable values in a given context (VariableNode), results of basic
+logic (IfNode), results of looping (ForNode), or anything else. The core Node
+types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
+define their own custom node types.
+
+Each Node has a render() method, which takes a Context and returns a string of
+the rendered node. For example, the render() method of a Variable Node returns
+the variable's value as a string. The render() method of an IfNode returns the
+rendered output of whatever was inside the loop, recursively.
+
+The Template class is a convenient wrapper that takes care of template
+compilation and rendering.
+
+Usage:
+
+The only thing you should ever use directly in this file is the Template class.
+Create a compiled template object with a template_string, then call render()
+with a context. In the compilation stage, the TemplateSyntaxError exception
+will be raised if the template doesn't have proper syntax.
+
+Sample code:
+
+>>> import template
+>>> s = '''
+...
+... {% if test %}
+...
{{ varvalue }}
+... {% endif %}
+...
+... '''
+>>> t = template.Template(s)
+
+(t is now a compiled template, and its render() method can be called multiple
+times with multiple contexts)
+
+>>> c = template.Context({'test':True, 'varvalue': 'Hello'})
+>>> t.render(c)
+'\n\n\n Hello
\n\n\n'
+>>> c = template.Context({'test':False, 'varvalue': 'Hello'})
+>>> t.render(c)
+'\n\n\n\n'
+"""
+import re
+from inspect import getargspec
+from django.utils.functional import curry
+from django.conf.settings import DEFAULT_CHARSET
+from django.conf import settings
+
+__all__ = ('Template','Context','compile_string')
+
+TOKEN_TEXT = 0
+TOKEN_VAR = 1
+TOKEN_BLOCK = 2
+
+# template syntax constants
+FILTER_SEPARATOR = '|'
+FILTER_ARGUMENT_SEPARATOR = ':'
+VARIABLE_ATTRIBUTE_SEPARATOR = '.'
+BLOCK_TAG_START = '{%'
+BLOCK_TAG_END = '%}'
+VARIABLE_TAG_START = '{{'
+VARIABLE_TAG_END = '}}'
+
+ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
+
+# what to report as the origin for templates that come from non-loader sources
+# (e.g. strings)
+UNKNOWN_SOURCE="<unknown source>"
+
+# match a variable or block tag and capture the entire tag, including start/end delimiters
+tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
+ re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
+
+# global dictionary of libraries that have been loaded using get_library
+libraries = {}
+# global list of libraries to load by default for a new parser
+builtins = []
+
+class TemplateSyntaxError(Exception):
+ pass
+
+class ContextPopException(Exception):
+ "pop() has been called more times than push()"
+ pass
+
+class TemplateDoesNotExist(Exception):
+ pass
+
+class VariableDoesNotExist(Exception):
+ pass
+
+class InvalidTemplateLibrary(Exception):
+ pass
+
+class Origin(object):
+ def __init__(self, name):
+ self.name = name
+
+ def reload(self):
+ raise NotImplementedError
+
+ def __str__(self):
+ return self.name
+
+class StringOrigin(Origin):
+ def __init__(self, source):
+ super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
+ self.source = source
+
+ def reload(self):
+ return self.source
+
+class Template:
+ def __init__(self, template_string, origin=None):
+ "Compilation stage"
+ if settings.TEMPLATE_DEBUG and origin == None:
+ origin = StringOrigin(template_string)
+ # Could do some crazy stack-frame stuff to record where this string
+ # came from...
+ self.nodelist = compile_string(template_string, origin)
+
+ def __iter__(self):
+ for node in self.nodelist:
+ for subnode in node:
+ yield subnode
+
+ def render(self, context):
+ "Display stage -- can be called many times"
+ return self.nodelist.render(context)
+
+def compile_string(template_string, origin):
+ "Compiles template_string into NodeList ready for rendering"
+ lexer = lexer_factory(template_string, origin)
+ parser = parser_factory(lexer.tokenize())
+ return parser.parse()
+
+class Context:
+ "A stack container for variable context"
+ def __init__(self, dict=None):
+ dict = dict or {}
+ self.dicts = [dict]
+
+ def __repr__(self):
+ return repr(self.dicts)
+
+ def __iter__(self):
+ for d in self.dicts:
+ yield d
+
+ def push(self):
+ self.dicts = [{}] + self.dicts
+
+ def pop(self):
+ if len(self.dicts) == 1:
+ raise ContextPopException
+ del self.dicts[0]
+
+ def __setitem__(self, key, value):
+ "Set a variable in the current context"
+ self.dicts[0][key] = value
+
+ def __getitem__(self, key):
+ "Get a variable's value, starting at the current context and going upward"
+ for dict in self.dicts:
+ if dict.has_key(key):
+ return dict[key]
+ return ''
+
+ def __delitem__(self, key):
+ "Delete a variable from the current context"
+ del self.dicts[0][key]
+
+ def has_key(self, key):
+ for dict in self.dicts:
+ if dict.has_key(key):
+ return True
+ return False
+
+ def get(self, key, otherwise):
+ for dict in self.dicts:
+ if dict.has_key(key):
+ return dict[key]
+ return otherwise
+
+ def update(self, other_dict):
+ "Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
+ self.dicts = [other_dict] + self.dicts
+
+class Token:
+ def __init__(self, token_type, contents):
+ "The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
+ self.token_type, self.contents = token_type, contents
+
+ def __str__(self):
+ return '<%s token: "%s...">' % (
+ {TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type],
+ self.contents[:20].replace('\n', '')
+ )
+
+ def __repr__(self):
+ return '<%s token: "%s">' % (
+ {TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type],
+ self.contents[:].replace('\n', '')
+ )
+
+class Lexer(object):
+ def __init__(self, template_string, origin):
+ self.template_string = template_string
+ self.origin = origin
+
+ def tokenize(self):
+ "Return a list of tokens from a given template_string"
+ # remove all empty strings, because the regex has a tendency to add them
+ bits = filter(None, tag_re.split(self.template_string))
+ return map(self.create_token, bits)
+
+ def create_token(self,token_string):
+ "Convert the given token string into a new Token object and return it"
+ if token_string.startswith(VARIABLE_TAG_START):
+ token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
+ elif token_string.startswith(BLOCK_TAG_START):
+ token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
+ else:
+ token = Token(TOKEN_TEXT, token_string)
+ return token
+
+class DebugLexer(Lexer):
+ def __init__(self, template_string, origin):
+ super(DebugLexer, self).__init__(template_string, origin)
+
+ def tokenize(self):
+ "Return a list of tokens from a given template_string"
+ token_tups, upto = [], 0
+ for match in tag_re.finditer(self.template_string):
+ start, end = match.span()
+ if start > upto:
+ token_tups.append( (self.template_string[upto:start], (upto, start)) )
+ upto = start
+ token_tups.append( (self.template_string[start:end], (start,end)) )
+ upto = end
+ last_bit = self.template_string[upto:]
+ if last_bit:
+ token_tups.append( (last_bit, (upto, upto + len(last_bit))) )
+ return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
+
+ def create_token(self, token_string, source):
+ token = super(DebugLexer, self).create_token(token_string)
+ token.source = source
+ return token
+
+class Parser(object):
+ def __init__(self, tokens):
+ self.tokens = tokens
+ self.tags = {}
+ self.filters = {}
+ for lib in builtins:
+ self.add_library(lib)
+
+ def parse(self, parse_until=[]):
+ nodelist = self.create_nodelist()
+ while self.tokens:
+ token = self.next_token()
+ if token.token_type == TOKEN_TEXT:
+ self.extend_nodelist(nodelist, TextNode(token.contents), token)
+ elif 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)
+ # execute callback function for this tag and append resulting node
+ self.enter_command(command, token)
+ try:
+ compile_func = self.tags[command]
+ except KeyError:
+ self.invalid_block_tag(token, command)
+ 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 create_variable_node(self, filter_expression):
+ return VariableNode(filter_expression)
+
+ def create_nodelist(self):
+ return NodeList()
+
+ def extend_nodelist(self, nodelist, node, token):
+ nodelist.append(node)
+
+ def enter_command(self, command, token):
+ pass
+
+ def exit_command(self):
+ pass
+
+ def error(self, token, msg ):
+ return TemplateSyntaxError(msg)
+
+ def empty_variable(self, token):
+ raise self.error( token, "Empty variable tag")
+
+ def empty_block_tag(self, token):
+ raise self.error( token, "Empty block tag")
+
+ def invalid_block_tag(self, token, command):
+ raise self.error( token, "Invalid block tag: '%s'" % command)
+
+ def unclosed_block_tag(self, parse_until):
+ raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
+
+ def compile_function_error(self, token, e):
+ pass
+
+ def next_token(self):
+ return self.tokens.pop(0)
+
+ def prepend_token(self, token):
+ self.tokens.insert(0, token)
+
+ def delete_first_token(self):
+ del self.tokens[0]
+
+ def add_library(self, lib):
+ self.tags.update(lib.tags)
+ self.filters.update(lib.filters)
+
+ def compile_filter(self,token):
+ "Convenient wrapper for FilterExpression"
+ return FilterExpression(token, self)
+
+ def find_filter(self, filter_name):
+ if self.filters.has_key(filter_name):
+ return self.filters[filter_name]
+ else:
+ raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
+
+class DebugParser(Parser):
+ def __init__(self, lexer):
+ super(DebugParser, self).__init__(lexer)
+ self.command_stack = []
+
+ def enter_command(self, command, token):
+ self.command_stack.append( (command, token.source) )
+
+ def exit_command(self):
+ self.command_stack.pop()
+
+ def error(self, token, msg):
+ return self.source_error(token.source, msg)
+
+ def source_error(self, source,msg):
+ e = TemplateSyntaxError(msg)
+ e.source = source
+ return e
+
+ def create_nodelist(self):
+ return DebugNodeList()
+
+ def create_variable_node(self, contents):
+ return DebugVariableNode(contents)
+
+ def extend_nodelist(self, nodelist, node, token):
+ node.source = token.source
+ super(DebugParser, self).extend_nodelist(nodelist, node, token)
+
+ def unclosed_block_tag(self, parse_until):
+ (command, source) = self.command_stack.pop()
+ msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until))
+ raise self.source_error( source, msg)
+
+ def compile_function_error(self, token, e):
+ if not hasattr(e, 'source'):
+ e.source = token.source
+
+
+def lexer_factory(*args, **kwargs):
+ if settings.TEMPLATE_DEBUG:
+ return DebugLexer(*args, **kwargs)
+ else:
+ return Lexer(*args, **kwargs)
+
+def parser_factory(*args, **kwargs):
+ if settings.TEMPLATE_DEBUG:
+ return DebugParser(*args, **kwargs)
+ else:
+ return Parser(*args, **kwargs)
+
+
+class TokenParser:
+ """
+ Subclass this and implement the top() method to parse a template line. When
+ instantiating the parser, pass in the line from the Django template parser.
+
+ The parser's "tagname" instance-variable stores the name of the tag that
+ the filter was called with.
+ """
+ def __init__(self, subject):
+ self.subject = subject
+ self.pointer = 0
+ self.backout = []
+ self.tagname = self.tag()
+
+ def top(self):
+ "Overload this method to do the actual parsing and return the result."
+ raise NotImplemented
+
+ def more(self):
+ "Returns True if there is more stuff in the tag."
+ return self.pointer < len(self.subject)
+
+ def back(self):
+ "Undoes the last microparser. Use this for lookahead and backtracking."
+ if not len(self.backout):
+ raise TemplateSyntaxError, "back called without some previous parsing"
+ self.pointer = self.backout.pop()
+
+ def tag(self):
+ "A microparser that just returns the next tag from the line."
+ subject = self.subject
+ i = self.pointer
+ if i >= len(subject):
+ raise TemplateSyntaxError, "expected another tag, found end of string: %s" % subject
+ p = i
+ while i < len(subject) and subject[i] not in (' ', '\t'):
+ i += 1
+ s = subject[p:i]
+ while i < len(subject) and subject[i] in (' ', '\t'):
+ i += 1
+ self.backout.append(self.pointer)
+ self.pointer = i
+ return s
+
+ def value(self):
+ "A microparser that parses for a value: some string constant or variable name."
+ subject = self.subject
+ i = self.pointer
+ if i >= len(subject):
+ raise TemplateSyntaxError, "Searching for value. Expected another value but found end of string: %s" % subject
+ if subject[i] in ('"', "'"):
+ p = i
+ i += 1
+ while i < len(subject) and subject[i] != subject[p]:
+ i += 1
+ if i >= len(subject):
+ raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
+ i += 1
+ res = subject[p:i]
+ while i < len(subject) and subject[i] in (' ', '\t'):
+ i += 1
+ self.backout.append(self.pointer)
+ self.pointer = i
+ return res
+ else:
+ p = i
+ while i < len(subject) and subject[i] not in (' ', '\t'):
+ if subject[i] in ('"', "'"):
+ c = subject[i]
+ i += 1
+ while i < len(subject) and subject[i] != c:
+ i += 1
+ if i >= len(subject):
+ raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
+ i += 1
+ s = subject[p:i]
+ while i < len(subject) and subject[i] in (' ', '\t'):
+ i += 1
+ self.backout.append(self.pointer)
+ self.pointer = i
+ return s
+
+
+
+
+filter_raw_string = r"""
+^%(i18n_open)s"(?P%(str)s)"%(i18n_close)s|
+^"(?P%(str)s)"|
+^(?P[%(var_chars)s]+)|
+ (?:%(filter_sep)s
+ (?P\w+)
+ (?:%(arg_sep)s
+ (?:
+ %(i18n_open)s"(?P%(str)s)"%(i18n_close)s|
+ "(?P%(str)s)"|
+ (?P[%(var_chars)s]+)
+ )
+ )?
+ )""" % {
+ 'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
+ 'var_chars': "A-Za-z0-9\_\." ,
+ 'filter_sep': re.escape(FILTER_SEPARATOR),
+ 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
+ 'i18n_open' : re.escape("_("),
+ 'i18n_close' : re.escape(")"),
+ }
+
+filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
+filter_re = re.compile(filter_raw_string)
+
+class FilterExpression(object):
+ """
+ Parses a variable token and its optional filters (all as a single string),
+ and return a list of tuples of the filter name and arguments.
+ Sample:
+ >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
+ >>> p = FilterParser(token)
+ >>> p.filters
+ [('default', 'Default value'), ('date', 'Y-m-d')]
+ >>> p.var
+ 'variable'
+
+ This class should never be instantiated outside of the
+ get_filters_from_token helper function.
+ """
+ def __init__(self, token, parser):
+ self.token = token
+ matches = filter_re.finditer(token)
+ var = None
+ filters = []
+ upto = 0
+ for match in matches:
+ start = match.start()
+ if upto != start:
+ raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \
+ (token[:upto], token[upto:start], token[start:])
+ if var == None:
+ var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
+ if i18n_constant:
+ var = '"%s"' % _(i18n_constant)
+ elif constant:
+ var = '"%s"' % constant
+ upto = match.end()
+ if var == None:
+ raise TemplateSyntaxError, "Could not find variable at start of %s" % token
+ elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
+ raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
+ else:
+ filter_name = match.group("filter_name")
+ args = []
+ constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
+ if i18n_arg:
+ args.append((False, _(i18n_arg.replace('\\', ''))))
+ elif constant_arg:
+ args.append((False, constant_arg.replace('\\', '')))
+ elif var_arg:
+ args.append((True, var_arg))
+ filter_func = parser.find_filter(filter_name)
+ self.args_check(filter_name,filter_func, args)
+ filters.append( (filter_func,args))
+ upto = match.end()
+ if upto != len(token):
+ raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
+ self.var , self.filters = var, filters
+
+ def resolve(self, context):
+ try:
+ obj = resolve_variable(self.var, context)
+ except VariableDoesNotExist:
+ obj = ''
+ for func, args in self.filters:
+ arg_vals = []
+ for lookup, arg in args:
+ if not lookup:
+ arg_vals.append(arg)
+ else:
+ arg_vals.append(resolve_variable(arg, context))
+ obj = func(obj, *arg_vals)
+ return obj
+
+ def args_check(name, func, provided):
+ provided = list(provided)
+ plen = len(provided)
+ (args, varargs, varkw, defaults) = getargspec(func)
+ # First argument is filter input.
+ args.pop(0)
+ if defaults:
+ nondefs = args[:-len(defaults)]
+ else:
+ nondefs = args
+ # Args without defaults must be provided.
+ try:
+ for arg in nondefs:
+ provided.pop(0)
+ except IndexError:
+ # Not enough
+ raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
+
+ # Defaults can be overridden.
+ defaults = defaults and list(defaults) or []
+ try:
+ for parg in provided:
+ defaults.pop(0)
+ except IndexError:
+ # Too many.
+ raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
+
+ return True
+ args_check = staticmethod(args_check)
+
+ def __str__(self):
+ return self.token
+
+def resolve_variable(path, context):
+ """
+ Returns the resolved variable, which may contain attribute syntax, within
+ the given context. The variable may be a hard-coded string (if it begins
+ and ends with single or double quote marks).
+
+ >>> c = {'article': {'section':'News'}}
+ >>> resolve_variable('article.section', c)
+ 'News'
+ >>> resolve_variable('article', c)
+ {'section': 'News'}
+ >>> class AClass: pass
+ >>> c = AClass()
+ >>> c.article = AClass()
+ >>> c.article.section = 'News'
+ >>> resolve_variable('article.section', c)
+ 'News'
+
+ (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
+ """
+ if path[0] in ('"', "'") and path[0] == path[-1]:
+ current = path[1:-1]
+ else:
+ current = context
+ bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
+ while bits:
+ try: # dictionary lookup
+ current = current[bits[0]]
+ except (TypeError, AttributeError, KeyError):
+ try: # attribute lookup
+ current = getattr(current, bits[0])
+ if callable(current):
+ if getattr(current, 'alters_data', False):
+ current = ''
+ else:
+ try: # method call (assuming no args required)
+ current = current()
+ except TypeError: # arguments *were* required
+ # GOTCHA: This will also catch any TypeError
+ # raised in the function itself.
+ current = '' # invalid method call
+ except Exception, e:
+ if getattr(e, 'silent_variable_failure', False):
+ current = ''
+ else:
+ raise
+ except (TypeError, AttributeError):
+ try: # list-index lookup
+ current = current[int(bits[0])]
+ except (IndexError, ValueError, KeyError):
+ raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute
+ del bits[0]
+ return current
+
+class Node:
+ def render(self, context):
+ "Return the node rendered as a string"
+ pass
+
+ def __iter__(self):
+ yield self
+
+ def get_nodes_by_type(self, nodetype):
+ "Return a list of all nodes (within this node and its nodelist) of the given type"
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ if hasattr(self, 'nodelist'):
+ nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
+ return nodes
+
+class NodeList(list):
+ def render(self, context):
+ bits = []
+ for node in self:
+ if isinstance(node, Node):
+ bits.append(self.render_node(node, context))
+ else:
+ bits.append(node)
+ return ''.join(bits)
+
+ def get_nodes_by_type(self, nodetype):
+ "Return a list of all nodes of the given type"
+ nodes = []
+ for node in self:
+ nodes.extend(node.get_nodes_by_type(nodetype))
+ return nodes
+
+ def render_node(self, node, context):
+ return(node.render(context))
+
+class DebugNodeList(NodeList):
+ def render_node(self, node, context):
+ try:
+ result = node.render(context)
+ except TemplateSyntaxError, e:
+ if not hasattr(e, 'source'):
+ e.source = node.source
+ raise
+ except Exception:
+ from sys import exc_info
+ wrapped = TemplateSyntaxError('Caught an exception while rendering.')
+ wrapped.source = node.source
+ wrapped.exc_info = exc_info()
+ raise wrapped
+ return result
+
+class TextNode(Node):
+ def __init__(self, s):
+ self.s = s
+
+ def __repr__(self):
+ return "" % self.s[:25]
+
+ def render(self, context):
+ return self.s
+
+class VariableNode(Node):
+ def __init__(self, filter_expression):
+ self.filter_expression = filter_expression
+
+ def __repr__(self):
+ return "" % self.filter_expression
+
+ def encode_output(self, output):
+ # Check type so that we don't run str() on a Unicode object
+ if not isinstance(output, basestring):
+ return str(output)
+ elif isinstance(output, unicode):
+ return output.encode(DEFAULT_CHARSET)
+ else:
+ return output
+
+ def render(self, context):
+ output = self.filter_expression.resolve(context)
+ return self.encode_output(output)
+
+class DebugVariableNode(VariableNode):
+ def render(self, context):
+ try:
+ output = self.filter_expression.resolve(context)
+ except TemplateSyntaxError, e:
+ if not hasattr(e, 'source'):
+ e.source = self.source
+ raise
+ return self.encode_output(output)
+
+def generic_tag_compiler(params, defaults, name, node_class, parser, token):
+ "Returns a template.Node subclass."
+ bits = token.contents.split()[1:]
+ bmax = len(params)
+ def_len = defaults and len(defaults) or 0
+ bmin = bmax - def_len
+ if(len(bits) < bmin or len(bits) > bmax):
+ if bmin == bmax:
+ message = "%s takes %s arguments" % (name, bmin)
+ else:
+ message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
+ raise TemplateSyntaxError, message
+ return node_class(bits)
+
+class Library(object):
+ def __init__(self):
+ self.filters = {}
+ self.tags = {}
+
+ def tag(self, name=None, compile_function=None):
+ if name == None and compile_function == None:
+ # @register.tag()
+ return self.tag_function
+ elif name != None and compile_function == None:
+ if(callable(name)):
+ # @register.tag
+ return self.tag_function(name)
+ else:
+ # @register.tag('somename') or @register.tag(name='somename')
+ def dec(func):
+ return self.tag(name, func)
+ return dec
+ elif name != None and compile_function != None:
+ # register.tag('somename', somefunc)
+ self.tags[name] = compile_function
+ return compile_function
+ else:
+ raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
+
+ def tag_function(self,func):
+ self.tags[func.__name__] = func
+ return func
+
+ def filter(self, name=None, filter_func=None):
+ if name == None and filter_func == None:
+ # @register.filter()
+ return self.filter_function
+ elif filter_func == None:
+ if(callable(name)):
+ # @register.filter
+ return self.filter_function(name)
+ else:
+ # @register.filter('somename') or @register.filter(name='somename')
+ def dec(func):
+ return self.filter(name, func)
+ return dec
+ elif name != None and filter_func != None:
+ # register.filter('somename', somefunc)
+ self.filters[name] = filter_func
+ return filter_func
+ else:
+ raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r, %r)", (name, compile_function, has_arg)
+
+ def filter_function(self, func):
+ self.filters[func.__name__] = func
+ return func
+
+ def simple_tag(self,func):
+ (params, xx, xxx, defaults) = getargspec(func)
+
+ class SimpleNode(Node):
+ def __init__(self, vars_to_resolve):
+ self.vars_to_resolve = vars_to_resolve
+
+ def render(self, context):
+ resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
+ return func(*resolved_vars)
+
+ compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode)
+ compile_func.__doc__ = func.__doc__
+ self.tag(func.__name__, compile_func)
+ return func
+
+ def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
+ def dec(func):
+ (params, xx, xxx, defaults) = getargspec(func)
+ if takes_context:
+ if params[0] == 'context':
+ params = params[1:]
+ else:
+ raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
+
+ class InclusionNode(Node):
+ def __init__(self, vars_to_resolve):
+ self.vars_to_resolve = vars_to_resolve
+
+ def render(self, context):
+ resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
+ if takes_context:
+ args = [context] + resolved_vars
+ else:
+ args = resolved_vars
+
+ dict = func(*args)
+
+ if not getattr(self, 'nodelist', False):
+ from django.core.template_loader import get_template
+ t = get_template(file_name)
+ self.nodelist = t.nodelist
+ return self.nodelist.render(context_class(dict))
+
+ compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode)
+ compile_func.__doc__ = func.__doc__
+ self.tag(func.__name__, compile_func)
+ return func
+ return dec
+
+def get_library(module_name):
+ lib = libraries.get(module_name, None)
+ if not lib:
+ try:
+ mod = __import__(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
+ return lib
+
+def add_to_builtins(module_name):
+ builtins.append(get_library(module_name))
+
+add_to_builtins('django.template.defaulttags')
+add_to_builtins('django.template.defaultfilters')
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
new file mode 100644
index 0000000000..34fffcb8a1
--- /dev/null
+++ b/django/template/defaultfilters.py
@@ -0,0 +1,487 @@
+"Default variable filters"
+
+from django.template import resolve_variable, Library
+from django.conf.settings import DATE_FORMAT, TIME_FORMAT
+from django.utils.translation import gettext
+import re
+import random as random_module
+
+register = Library()
+
+###################
+# STRINGS #
+###################
+
+
+def addslashes(value):
+ "Adds slashes - useful for passing strings to JavaScript, for example."
+ return value.replace('"', '\\"').replace("'", "\\'")
+
+def capfirst(value):
+ "Capitalizes the first character of the value"
+ value = str(value)
+ return value and value[0].upper() + value[1:]
+
+def fix_ampersands(value):
+ "Replaces ampersands with ``&`` entities"
+ from django.utils.html import fix_ampersands
+ return fix_ampersands(value)
+
+def floatformat(text):
+ """
+ Displays a floating point number as 34.2 (with one decimal place) -- but
+ only if there's a point to be displayed
+ """
+ try:
+ f = float(text)
+ except ValueError:
+ return ''
+ m = f - int(f)
+ if m:
+ return '%.1f' % f
+ else:
+ return '%d' % int(f)
+
+def linenumbers(value):
+ "Displays text with line numbers"
+ from django.utils.html import escape
+ lines = value.split('\n')
+ # Find the maximum width of the line count, for use with zero padding string format command
+ width = str(len(str(len(lines))))
+ for i, line in enumerate(lines):
+ lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
+ return '\n'.join(lines)
+
+def lower(value):
+ "Converts a string into all lowercase"
+ return value.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(str(value))
+
+def slugify(value):
+ "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
+ value = re.sub('[^\w\s-]', '', value).strip().lower()
+ return re.sub('\s+', '-', value)
+
+def stringformat(value, arg):
+ """
+ Formats the variable according to the argument, 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 ("%" + arg) % value
+ except (ValueError, TypeError):
+ return ""
+
+def title(value):
+ "Converts a string into titlecase"
+ return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.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.
+ if not isinstance(value, basestring):
+ value = str(value)
+ return truncate_words(value, length)
+
+def upper(value):
+ "Converts a string into all uppercase"
+ return value.upper()
+
+def urlencode(value):
+ "Escapes a value for use in a URL"
+ import urllib
+ return urllib.quote(value)
+
+def urlize(value):
+ "Converts URLs in plain text into clickable links"
+ from django.utils.html import urlize
+ return urlize(value, nofollow=True)
+
+def urlizetrunc(value, limit):
+ """
+ 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 urlize(value, trim_url_limit=int(limit), nofollow=True)
+
+def wordcount(value):
+ "Returns the number of words"
+ return len(value.split())
+
+def wordwrap(value, arg):
+ """
+ Wraps words at specified line length
+
+ Argument: number of words to wrap the text at.
+ """
+ from django.utils.text import wrap
+ return wrap(str(value), int(arg))
+
+def ljust(value, arg):
+ """
+ Left-aligns the value in a field of a given width
+
+ Argument: field size
+ """
+ return str(value).ljust(int(arg))
+
+def rjust(value, arg):
+ """
+ Right-aligns the value in a field of a given width
+
+ Argument: field size
+ """
+ return str(value).rjust(int(arg))
+
+def center(value, arg):
+ "Centers the value in a field of a given width"
+ return str(value).center(int(arg))
+
+def cut(value, arg):
+ "Removes all values of arg from the given string"
+ return value.replace(arg, '')
+
+###################
+# HTML STRINGS #
+###################
+
+def escape(value):
+ "Escapes a string's HTML"
+ from django.utils.html import escape
+ return escape(value)
+
+def linebreaks(value):
+ "Converts newlines into and
s"
+ from django.utils.html import linebreaks
+ return linebreaks(value)
+
+def linebreaksbr(value):
+ "Converts newlines into
s"
+ return value.replace('\n', '
')
+
+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 = '(%s)' % '|'.join(tags)
+ starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re)
+ endtag_re = re.compile('%s>' % tags_re)
+ value = starttag_re.sub('', value)
+ value = endtag_re.sub('', value)
+ return value
+
+def striptags(value):
+ "Strips all [X]HTML tags"
+ from django.utils.html import strip_tags
+ if not isinstance(value, basestring):
+ value = str(value)
+ return strip_tags(value)
+
+###################
+# LISTS #
+###################
+
+def dictsort(value, arg):
+ """
+ Takes a list of dicts, returns that list sorted by the property given in
+ the argument.
+ """
+ decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated.sort()
+ return [item[1] for item in decorated]
+
+def dictsortreversed(value, arg):
+ """
+ Takes a list of dicts, returns that list sorted in reverse order by the
+ property given in the argument.
+ """
+ decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated.sort()
+ decorated.reverse()
+ return [item[1] for item in decorated]
+
+def first(value):
+ "Returns the first item in a list"
+ try:
+ return value[0]
+ except IndexError:
+ return ''
+
+def join(value, arg):
+ "Joins a list with a string, like Python's ``str.join(list)``"
+ try:
+ return arg.join(map(str, value))
+ except AttributeError: # fail silently but nicely
+ return value
+
+def length(value):
+ "Returns the length of the value - useful for lists"
+ return len(value)
+
+def length_is(value, arg):
+ "Returns a boolean of whether the value's length is the argument"
+ return len(value) == int(arg)
+
+def random(value):
+ "Returns a random item from the list"
+ return random_module.choice(value)
+
+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(':'):
+ if len(x) == 0:
+ bits.append(None)
+ else:
+ bits.append(int(x))
+ return value[slice(*bits)]
+
+ except (ValueError, TypeError):
+ return value # Fail silently.
+
+def unordered_list(value):
+ """
+ Recursively takes a self-nested list and returns an HTML unordered list --
+ WITHOUT opening and closing
tags.
+
+ The list is assumed to be in the proper format. For example, if ``var`` contains
+ ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
+ then ``{{ var|unordered_list }}`` would return::
+
+ - States
+
+ - Kansas
+
+
+ - Illinois
+
+
+ """
+ def _helper(value, tabs):
+ indent = '\t' * tabs
+ if value[1]:
+ return '%s- %s\n%s\n%s
' % (indent, value[0], indent,
+ '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
+ else:
+ return '%s- %s
' % (indent, value[0])
+ return _helper(value, 1)
+
+###################
+# INTEGERS #
+###################
+
+def add(value, arg):
+ "Adds the arg to the value"
+ return int(value) + int(arg)
+
+def get_digit(value, arg):
+ """
+ Given a whole number, returns the requested digit of it, where 1 is the
+ right-most digit, 2 is the second-right-most digit, etc. Returns the
+ original value for invalid input (if input or argument is not an integer,
+ or if argument is less than 1). Otherwise, output is always an integer.
+ """
+ try:
+ arg = int(arg)
+ value = int(value)
+ except ValueError:
+ return value # Fail silently for an invalid argument
+ if arg < 1:
+ return value
+ try:
+ return int(str(value)[-arg])
+ except IndexError:
+ return 0
+
+###################
+# DATES #
+###################
+
+def date(value, arg=DATE_FORMAT):
+ "Formats a date according to the given format"
+ from django.utils.dateformat import format
+ return format(value, arg)
+
+def time(value, arg=TIME_FORMAT):
+ "Formats a time according to the given format"
+ from django.utils.dateformat import time_format
+ return time_format(value, arg)
+
+def timesince(value):
+ 'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
+ from django.utils.timesince import timesince
+ return timesince(value)
+
+###################
+# LOGIC #
+###################
+
+def default(value, arg):
+ "If value is unavailable, use given default"
+ return value or arg
+
+def default_if_none(value, arg):
+ "If value is None, use given default"
+ if value is None:
+ return arg
+ return value
+
+def divisibleby(value, arg):
+ "Returns true if the value is devisible by the argument"
+ return int(value) % int(arg) == 0
+
+def yesno(value, arg=None):
+ """
+ Given a string mapping values for true, false and (optionally) None,
+ returns one of those strings accoding to the value:
+
+ ========== ====================== ==================================
+ Value Argument Outputs
+ ========== ====================== ==================================
+ ``True`` ``"yeah,no,maybe"`` ``yeah``
+ ``False`` ``"yeah,no,maybe"`` ``no``
+ ``None`` ``"yeah,no,maybe"`` ``maybe``
+ ``None`` ``"yeah,no"`` ``"no"`` (converts None to False
+ if no mapping for None is given.
+ ========== ====================== ==================================
+ """
+ if arg is None:
+ arg = gettext('yes,no,maybe')
+ bits = arg.split(',')
+ if len(bits) < 2:
+ return value # Invalid arg.
+ try:
+ yes, no, maybe = bits
+ except ValueError: # unpack list of wrong size (no "maybe" value provided)
+ yes, no, maybe = bits[0], bits[1], bits[1]
+ if value is None:
+ return maybe
+ if value:
+ return yes
+ return no
+
+###################
+# MISC #
+###################
+
+def filesizeformat(bytes):
+ """
+ Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
+ bytes, etc).
+ """
+ bytes = float(bytes)
+ if bytes < 1024:
+ return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
+ if bytes < 1024 * 1024:
+ return "%.1f KB" % (bytes / 1024)
+ if bytes < 1024 * 1024 * 1024:
+ return "%.1f MB" % (bytes / (1024 * 1024))
+ return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
+
+def pluralize(value):
+ "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'"
+ try:
+ if int(value) != 1:
+ return 's'
+ except ValueError: # invalid string that's not a number
+ pass
+ except TypeError: # value isn't a string or a number; maybe it's a list?
+ try:
+ if len(value) != 1:
+ return 's'
+ except TypeError: # len() of unsized object
+ pass
+ return ''
+
+def phone2numeric(value):
+ "Takes a phone number and converts it in to its numerical equivalent"
+ from django.utils.text import phone2numeric
+ return phone2numeric(value)
+
+def pprint(value):
+ "A wrapper around pprint.pprint -- for debugging, really"
+ from pprint import pformat
+ try:
+ return pformat(value)
+ except Exception, e:
+ return "Error in formatting:%s" % e
+
+# Syntax: register.filter(name of filter, callback)
+register.filter(add)
+register.filter(addslashes)
+register.filter(capfirst)
+register.filter(center)
+register.filter(cut)
+register.filter(date)
+register.filter(default)
+register.filter(default_if_none)
+register.filter(dictsort)
+register.filter(dictsortreversed)
+register.filter(divisibleby)
+register.filter(escape)
+register.filter(filesizeformat)
+register.filter(first)
+register.filter(fix_ampersands)
+register.filter(floatformat)
+register.filter(get_digit)
+register.filter(join)
+register.filter(length)
+register.filter(length_is)
+register.filter(linebreaks)
+register.filter(linebreaksbr)
+register.filter(linenumbers)
+register.filter(ljust)
+register.filter(lower)
+register.filter(make_list)
+register.filter(phone2numeric)
+register.filter(pluralize)
+register.filter(pprint)
+register.filter(removetags)
+register.filter(random)
+register.filter(rjust)
+register.filter('slice', slice_)
+register.filter(slugify)
+register.filter(stringformat)
+register.filter(striptags)
+register.filter(time)
+register.filter(timesince)
+register.filter(title)
+register.filter(truncatewords)
+register.filter(unordered_list)
+register.filter(upper)
+register.filter(urlencode)
+register.filter(urlize)
+register.filter(urlizetrunc)
+register.filter(wordcount)
+register.filter(wordwrap)
+register.filter(yesno)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
new file mode 100644
index 0000000000..956d736c46
--- /dev/null
+++ b/django/template/defaulttags.py
@@ -0,0 +1,783 @@
+"Default tags used by the template system, available to all templates."
+
+from django.template import Node, NodeList, Template, Context, resolve_variable
+from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END
+from django.template import get_library, Library, InvalidTemplateLibrary
+import sys
+
+register = Library()
+
+class CommentNode(Node):
+ def render(self, context):
+ return ''
+
+class CycleNode(Node):
+ def __init__(self, cyclevars):
+ self.cyclevars = cyclevars
+ self.cyclevars_len = len(cyclevars)
+ self.counter = -1
+
+ def render(self, context):
+ self.counter += 1
+ return self.cyclevars[self.counter % self.cyclevars_len]
+
+class DebugNode(Node):
+ def render(self, context):
+ from pprint import pformat
+ output = [pformat(val) for val in context]
+ output.append('\n\n')
+ 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
+
+ def render(self, context):
+ output = self.nodelist.render(context)
+ # apply filters
+ return self.filter_expr.resolve(Context({'var': output}))
+
+class FirstOfNode(Node):
+ def __init__(self, vars):
+ self.vars = vars
+
+ def render(self, context):
+ for var in self.vars:
+ value = resolve_variable(var, context)
+ if value:
+ return str(value)
+ return ''
+
+class ForNode(Node):
+ def __init__(self, loopvar, sequence, reversed, nodelist_loop):
+ self.loopvar, self.sequence = loopvar, sequence
+ self.reversed = reversed
+ self.nodelist_loop = nodelist_loop
+
+ def __repr__(self):
+ if self.reversed:
+ reversed = ' reversed'
+ else:
+ reversed = ''
+ return "" % \
+ (self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
+
+ def __iter__(self):
+ for node in self.nodelist_loop:
+ yield node
+
+ def get_nodes_by_type(self, nodetype):
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
+ return nodes
+
+ def render(self, context):
+ nodelist = NodeList()
+ if context.has_key('forloop'):
+ parentloop = context['forloop']
+ else:
+ parentloop = {}
+ context.push()
+ try:
+ values = self.sequence.resolve(context)
+ except VariableDoesNotExist:
+ values = []
+ if values is None:
+ values = []
+ len_values = len(values)
+ if self.reversed:
+ # From http://www.python.org/doc/current/tut/node11.html
+ def reverse(data):
+ for index in range(len(data)-1, -1, -1):
+ yield data[index]
+ values = reverse(values)
+ for i, item in enumerate(values):
+ context['forloop'] = {
+ # shortcuts for current loop iteration number
+ 'counter0': i,
+ 'counter': i+1,
+ # reverse counter iteration numbers
+ 'revcounter': len_values - i,
+ 'revcounter0': len_values - i - 1,
+ # boolean values designating first and last times through loop
+ 'first': (i == 0),
+ 'last': (i == len_values - 1),
+ 'parentloop': parentloop,
+ }
+ context[self.loopvar] = item
+ for node in self.nodelist_loop:
+ nodelist.append(node.render(context))
+ context.pop()
+ return nodelist.render(context)
+
+class IfChangedNode(Node):
+ def __init__(self, nodelist):
+ self.nodelist = nodelist
+ self._last_seen = None
+
+ def render(self, context):
+ content = self.nodelist.render(context)
+ if content != self._last_seen:
+ firstloop = (self._last_seen == None)
+ self._last_seen = content
+ context.push()
+ context['ifchanged'] = {'firstloop': firstloop}
+ content = self.nodelist.render(context)
+ context.pop()
+ return content
+ else:
+ return ''
+
+class IfEqualNode(Node):
+ def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
+ self.var1, self.var2 = var1, var2
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.negate = negate
+
+ def __repr__(self):
+ return ""
+
+ def render(self, context):
+ val1 = resolve_variable(self.var1, context)
+ val2 = resolve_variable(self.var2, context)
+ if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
+ return self.nodelist_true.render(context)
+ return self.nodelist_false.render(context)
+
+class IfNode(Node):
+ def __init__(self, bool_exprs, nodelist_true, nodelist_false):
+ self.bool_exprs = bool_exprs
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+
+ def __repr__(self):
+ return ""
+
+ def __iter__(self):
+ for node in self.nodelist_true:
+ yield node
+ for node in self.nodelist_false:
+ yield node
+
+ def get_nodes_by_type(self, nodetype):
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
+ nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
+ return nodes
+
+ def render(self, context):
+ for ifnot, bool_expr in self.bool_exprs:
+ try:
+ value = bool_expr.resolve(context)
+ except VariableDoesNotExist:
+ value = None
+ if (value and not ifnot) or (ifnot and not value):
+ return self.nodelist_true.render(context)
+ return self.nodelist_false.render(context)
+
+class RegroupNode(Node):
+ def __init__(self, target, expression, var_name):
+ self.target, self.expression = target, expression
+ self.var_name = var_name
+
+ def render(self, context):
+ obj_list = self.target.resolve(context)
+ if obj_list == '': # target_var wasn't found in context; fail silently
+ context[self.var_name] = []
+ return ''
+ output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
+ for obj in obj_list:
+ grouper = self.expression.resolve(Context({'var': obj}))
+ # TODO: Is this a sensible way to determine equality?
+ if output and repr(output[-1]['grouper']) == repr(grouper):
+ output[-1]['list'].append(obj)
+ else:
+ output.append({'grouper': grouper, 'list': [obj]})
+ context[self.var_name] = output
+ return ''
+
+def include_is_allowed(filepath):
+ from django.conf.settings import ALLOWED_INCLUDE_ROOTS
+ for root in ALLOWED_INCLUDE_ROOTS:
+ if filepath.startswith(root):
+ return True
+ return False
+
+class SsiNode(Node):
+ def __init__(self, filepath, parsed):
+ self.filepath, self.parsed = filepath, parsed
+
+ def render(self, context):
+ from django.conf.settings import DEBUG
+ if not include_is_allowed(self.filepath):
+ if DEBUG:
+ return "[Didn't have permission to include file]"
+ else:
+ return '' # Fail silently for invalid includes.
+ try:
+ fp = open(self.filepath, 'r')
+ output = fp.read()
+ fp.close()
+ except IOError:
+ output = ''
+ if self.parsed:
+ try:
+ t = Template(output)
+ return t.render(context)
+ except TemplateSyntaxError, e:
+ if DEBUG:
+ return "[Included template had syntax error: %s]" % e
+ else:
+ 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
+
+ def render(self, context):
+ from datetime import datetime
+ from django.utils.dateformat import DateFormat
+ df = DateFormat(datetime.now())
+ return df.format(self.format_string)
+
+class TemplateTagNode(Node):
+ mapping = {'openblock': BLOCK_TAG_START,
+ 'closeblock': BLOCK_TAG_END,
+ 'openvariable': VARIABLE_TAG_START,
+ 'closevariable': VARIABLE_TAG_END}
+
+ def __init__(self, tagtype):
+ self.tagtype = tagtype
+
+ def render(self, context):
+ return self.mapping.get(self.tagtype, '')
+
+class WidthRatioNode(Node):
+ def __init__(self, val_expr, max_expr, max_width):
+ self.val_expr = val_expr
+ self.max_expr = max_expr
+ self.max_width = max_width
+
+ def render(self, context):
+ try:
+ value = self.val_expr.resolve(context)
+ maxvalue = self.max_expr.resolve(context)
+ except VariableDoesNotExist:
+ return ''
+ try:
+ value = float(value)
+ maxvalue = float(maxvalue)
+ ratio = (value / maxvalue) * int(self.max_width)
+ except (ValueError, ZeroDivisionError):
+ return ''
+ return str(int(round(ratio)))
+
+#@register.tag
+def comment(parser, token):
+ """
+ Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
+ """
+ nodelist = parser.parse(('endcomment',))
+ parser.delete_first_token()
+ return CommentNode()
+comment = register.tag(comment)
+
+#@register.tag
+def cycle(parser, token):
+ """
+ Cycle among the given strings each time this tag is encountered
+
+ Within a loop, cycles among the given strings each time through
+ the loop::
+
+ {% for o in some_list %}
+
+ ...
+
+ {% endfor %}
+
+ Outside of a loop, give the values a unique name the first time you call
+ it, then use that name each sucessive time through::
+
+ ...
+ ...
+ ...
+
+ You can use any number of values, seperated by commas. Make sure not to
+ put spaces between the values -- only commas.
+ """
+
+ # Note: This returns the exact same node on each {% cycle name %} call; that
+ # is, the node object returned from {% cycle a,b,c as name %} and the one
+ # returned from {% cycle name %} are the exact same object. This shouldn't
+ # cause problems (heh), but if it does, now you know.
+ #
+ # Ugly hack warning: this stuffs the named template dict into parser so
+ # that names are only unique within each template (as opposed to using
+ # a global variable, which would make cycle names have to be unique across
+ # *all* templates.
+
+ args = token.contents.split()
+ if len(args) < 2:
+ raise TemplateSyntaxError("'Cycle' statement requires at least two arguments")
+
+ elif len(args) == 2 and "," in args[1]:
+ # {% cycle a,b,c %}
+ cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
+ return CycleNode(cyclevars)
+ # {% cycle name %}
+
+ elif len(args) == 2:
+ name = args[1]
+ if not parser._namedCycleNodes.has_key(name):
+ raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
+ return parser._namedCycleNodes[name]
+
+ elif len(args) == 4:
+ # {% cycle a,b,c as name %}
+ if args[2] != 'as':
+ raise TemplateSyntaxError("Second 'cycle' argument must be 'as'")
+ cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
+ name = args[3]
+ node = CycleNode(cyclevars)
+
+ if not hasattr(parser, '_namedCycleNodes'):
+ parser._namedCycleNodes = {}
+
+ parser._namedCycleNodes[name] = node
+ return node
+
+ else:
+ raise TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
+cycle = register.tag(cycle)
+
+def debug(parser, token):
+ return DebugNode()
+debug = register.tag(debug)
+
+#@register.tag(name="filter")
+def do_filter(parser, token):
+ """
+ Filter the contents of the blog through variable filters.
+
+ Filters can also be piped through each other, and they can have
+ arguments -- just like in variable syntax.
+
+ Sample usage::
+
+ {% filter escape|lower %}
+ This text will be HTML-escaped, and will appear in lowercase.
+ {% endfilter %}
+ """
+ _, rest = token.contents.split(None, 1)
+ filter_expr = parser.compile_filter("var|%s" % (rest))
+ nodelist = parser.parse(('endfilter',))
+ parser.delete_first_token()
+ return FilterNode(filter_expr, nodelist)
+filter = register.tag("filter", do_filter)
+
+#@register.tag
+def firstof(parser, token):
+ """
+ Outputs the first variable passed that is not False.
+
+ Outputs nothing if all the passed variables are False.
+
+ Sample usage::
+
+ {% firstof var1 var2 var3 %}
+
+ This is equivalent to::
+
+ {% if var1 %}
+ {{ var1 }}
+ {% else %}{% if var2 %}
+ {{ var2 }}
+ {% else %}{% if var3 %}
+ {{ var3 }}
+ {% endif %}{% endif %}{% endif %}
+
+ but obviously much cleaner!
+ """
+ bits = token.contents.split()[1:]
+ if len(bits) < 1:
+ raise TemplateSyntaxError, "'firstof' statement requires at least one argument"
+ return FirstOfNode(bits)
+firstof = register.tag(firstof)
+
+#@register.tag(name="for")
+def do_for(parser, token):
+ """
+ Loop over each item in an array.
+
+ For example, to display a list of athletes given ``athlete_list``::
+
+
+ {% for athlete in athlete_list %}
+ - {{ athlete.name }}
+ {% endfor %}
+
+
+ You can also loop over a list in reverse by using
+ ``{% for obj in list reversed %}``.
+
+ The for loop sets a number of variables available within the loop:
+
+ ========================== ================================================
+ Variable Description
+ ========================== ================================================
+ ``forloop.counter`` The current iteration of the loop (1-indexed)
+ ``forloop.counter0`` The current iteration of the loop (0-indexed)
+ ``forloop.revcounter`` The number of iterations from the end of the
+ loop (1-indexed)
+ ``forloop.revcounter0`` The number of iterations from the end of the
+ loop (0-indexed)
+ ``forloop.first`` True if this is the first time through the loop
+ ``forloop.last`` True if this is the last time through the loop
+ ``forloop.parentloop`` For nested loops, this is the loop "above" the
+ current one
+ ========================== ================================================
+
+ """
+ bits = token.contents.split()
+ if len(bits) == 5 and bits[4] != 'reversed':
+ raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
+ if len(bits) not in (4, 5):
+ raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
+ if bits[2] != 'in':
+ raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
+ loopvar = bits[1]
+ sequence = parser.compile_filter(bits[3])
+ reversed = (len(bits) == 5)
+ nodelist_loop = parser.parse(('endfor',))
+ parser.delete_first_token()
+ return ForNode(loopvar, sequence, reversed, nodelist_loop)
+do_for = register.tag("for", do_for)
+
+def do_ifequal(parser, token, negate):
+ """
+ Output the contents of the block if the two arguments equal/don't equal each other.
+
+ Examples::
+
+ {% ifequal user.id comment.user_id %}
+ ...
+ {% endifequal %}
+
+ {% ifnotequal user.id comment.user_id %}
+ ...
+ {% else %}
+ ...
+ {% endifnotequal %}
+ """
+ bits = token.contents.split()
+ if len(bits) != 3:
+ raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
+ end_tag = 'end' + bits[0]
+ nodelist_true = parser.parse(('else', end_tag))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse((end_tag,))
+ parser.delete_first_token()
+ else:
+ nodelist_false = NodeList()
+ return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
+
+#@register.tag
+def ifequal(parser, token):
+ return do_ifequal(parser, token, False)
+ifequal = register.tag(ifequal)
+
+#@register.tag
+def ifnotequal(parser, token):
+ return do_ifequal(parser, token, True)
+ifnotequal = register.tag(ifnotequal)
+
+#@register.tag(name="if")
+def do_if(parser, token):
+ """
+ The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
+ (i.e. exists, is not empty, and is not a false boolean value) the contents
+ of the block are output:
+
+ ::
+
+ {% if althlete_list %}
+ Number of athletes: {{ althete_list|count }}
+ {% else %}
+ No athletes.
+ {% endif %}
+
+ In the above, if ``athlete_list`` is not empty, the number of athletes will
+ be displayed by the ``{{ athlete_list|count }}`` variable.
+
+ As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
+ will be displayed if the test fails.
+
+ ``if`` tags may use ``or`` or ``not`` to test a number of variables or to
+ negate a given variable::
+
+ {% if not athlete_list %}
+ There are no athletes.
+ {% endif %}
+
+ {% if athlete_list or coach_list %}
+ There are some athletes or some coaches.
+ {% endif %}
+
+ {% if not athlete_list or coach_list %}
+ There are no athletes, or there are some coaches.
+ {% endif %}
+
+ For simplicity, ``if`` tags do not allow ``and`` clauses. Use nested ``if``
+ tags instead::
+
+ {% if athlete_list %}
+ {% if coach_list %}
+ Number of athletes: {{ athlete_list|count }}.
+ Number of coaches: {{ coach_list|count }}.
+ {% endif %}
+ {% endif %}
+ """
+ bits = token.contents.split()
+ del bits[0]
+ if not bits:
+ raise TemplateSyntaxError, "'if' statement requires at least one argument"
+ # bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
+ boolpairs = ' '.join(bits).split(' or ')
+ boolvars = []
+ for boolpair in boolpairs:
+ if ' ' in boolpair:
+ not_, boolvar = boolpair.split()
+ if not_ != 'not':
+ raise TemplateSyntaxError, "Expected 'not' in if statement"
+ boolvars.append((True, parser.compile_filter(boolvar)))
+ else:
+ boolvars.append((False, parser.compile_filter(boolpair)))
+ nodelist_true = parser.parse(('else', 'endif'))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse(('endif',))
+ parser.delete_first_token()
+ else:
+ nodelist_false = NodeList()
+ return IfNode(boolvars, nodelist_true, nodelist_false)
+do_if = register.tag("if", do_if)
+
+#@register.tag
+def ifchanged(parser, token):
+ """
+ Check if a value has changed from the last iteration of a loop.
+
+ The 'ifchanged' block tag is used within a loop. It checks its own rendered
+ contents against its previous state and only displays its content if the
+ value has changed::
+
+ Archive for {{ year }}
+
+ {% for date in days %}
+ {% ifchanged %}{{ date|date:"F" }}
{% endifchanged %}
+ {{ date|date:"j" }}
+ {% endfor %}
+ """
+ bits = token.contents.split()
+ if len(bits) != 1:
+ raise TemplateSyntaxError, "'ifchanged' tag takes no arguments"
+ nodelist = parser.parse(('endifchanged',))
+ parser.delete_first_token()
+ return IfChangedNode(nodelist)
+ifchanged = register.tag(ifchanged)
+
+#@register.tag
+def ssi(parser, token):
+ """
+ Output the contents of a given file into the page.
+
+ Like a simple "include" tag, the ``ssi`` tag includes the contents
+ of another file -- which must be specified using an absolute page --
+ in the current page::
+
+ {% ssi /home/html/ljworld.com/includes/right_generic.html %}
+
+ If the optional "parsed" parameter is given, the contents of the included
+ file are evaluated as template code, with the current context::
+
+ {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
+ """
+ bits = token.contents.split()
+ parsed = False
+ if len(bits) not in (2, 3):
+ raise TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included"
+ if len(bits) == 3:
+ if bits[2] == 'parsed':
+ parsed = True
+ else:
+ raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
+ return SsiNode(bits[1], parsed)
+ssi = register.tag(ssi)
+
+#@register.tag
+def load(parser, token):
+ """
+ Load a custom template tag set.
+
+ For example, to load the template tags in ``django/templatetags/news/photos.py``::
+
+ {% load news.photos %}
+ """
+ bits = token.contents.split()
+ for taglib in bits[1:]:
+ # add the library to the parser
+ try:
+ lib = get_library("django.templatetags.%s" % taglib.split('.')[-1])
+ parser.add_library(lib)
+ except InvalidTemplateLibrary, e:
+ raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
+ return LoadNode()
+load = register.tag(load)
+
+#@register.tag
+def now(parser, token):
+ """
+ Display the date, formatted according to the given string.
+
+ Uses the same format as PHP's ``date()`` function; see http://php.net/date
+ for all the possible values.
+
+ Sample usage::
+
+ It is {% now "jS F Y H:i" %}
+ """
+ bits = token.contents.split('"')
+ if len(bits) != 3:
+ raise TemplateSyntaxError, "'now' statement takes one argument"
+ format_string = bits[1]
+ return NowNode(format_string)
+now = register.tag(now)
+
+#@register.tag
+def regroup(parser, token):
+ """
+ Regroup a list of alike objects by a common attribute.
+
+ This complex tag is best illustrated by use of an example: say that
+ ``people`` is a list of ``Person`` objects that have ``first_name``,
+ ``last_name``, and ``gender`` attributes, and you'd like to display a list
+ that looks like:
+
+ * Male:
+ * George Bush
+ * Bill Clinton
+ * Female:
+ * Margaret Thatcher
+ * Colendeeza Rice
+ * Unknown:
+ * Pat Smith
+
+ The following snippet of template code would accomplish this dubious task::
+
+ {% regroup people by gender as grouped %}
+
+ {% for group in grouped %}
+ - {{ group.grouper }}
+
+ {% for item in group.list %}
+ - {{ item }}
+ {% endfor %}
+
+ {% endfor %}
+
+
+ As you can see, ``{% regroup %}`` populates a variable with a list of
+ objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the
+ item that was grouped by; ``list`` contains the list of objects that share
+ that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female``
+ and ``Unknown``, and ``list`` is the list of people with those genders.
+
+ Note that `{% regroup %}`` does not work when the list to be grouped is not
+ sorted by the key you are grouping by! This means that if your list of
+ people was not sorted by gender, you'd need to make sure it is sorted before
+ using it, i.e.::
+
+ {% regroup people|dictsort:"gender" by gender as grouped %}
+
+ """
+ firstbits = token.contents.split(None, 3)
+ if len(firstbits) != 4:
+ raise TemplateSyntaxError, "'regroup' tag takes five arguments"
+ target = parser.compile_filter(firstbits[1])
+ if firstbits[2] != 'by':
+ raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
+ lastbits_reversed = firstbits[3][::-1].split(None, 2)
+ if lastbits_reversed[1][::-1] != 'as':
+ raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
+
+ expression = parser.compile_filter('var.%s' % lastbits_reversed[2][::-1])
+
+ var_name = lastbits_reversed[0][::-1]
+ return RegroupNode(target, expression, var_name)
+regroup = register.tag(regroup)
+
+#@register.tag
+def templatetag(parser, token):
+ """
+ Output one of the bits used to compose template tags.
+
+ Since the template system has no concept of "escaping", to display one of
+ the bits used in template tags, you must use the ``{% templatetag %}`` tag.
+
+ The argument tells which template bit to output:
+
+ ================== =======
+ Argument Outputs
+ ================== =======
+ ``openblock`` ``{%``
+ ``closeblock`` ``%}``
+ ``openvariable`` ``{{``
+ ``closevariable`` ``}}``
+ ================== =======
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'templatetag' statement takes one argument"
+ tag = bits[1]
+ if not TemplateTagNode.mapping.has_key(tag):
+ raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
+ (tag, TemplateTagNode.mapping.keys())
+ return TemplateTagNode(tag)
+templatetag = register.tag(templatetag)
+
+#@register.tag
+def widthratio(parser, token):
+ """
+ For creating bar charts and such, this tag calculates the ratio of a given
+ value to a maximum value, and then applies that ratio to a constant.
+
+ For example::
+
+
+
+ Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
+ the above example will be 88 pixels wide (because 175/200 = .875; .875 *
+ 100 = 87.5 which is rounded up to 88).
+ """
+ bits = token.contents.split()
+ if len(bits) != 4:
+ raise TemplateSyntaxError("widthratio takes three arguments")
+ tag, this_value_expr, max_value_expr, max_width = bits
+ try:
+ max_width = int(max_width)
+ except ValueError:
+ raise TemplateSyntaxError("widthratio final argument must be an integer")
+ return WidthRatioNode(parser.compile_filter(this_value_expr),
+ parser.compile_filter(max_value_expr), max_width)
+widthratio = register.tag(widthratio)
diff --git a/django/template/loader.py b/django/template/loader.py
new file mode 100644
index 0000000000..1172410557
--- /dev/null
+++ b/django/template/loader.py
@@ -0,0 +1,111 @@
+# Wrapper for loading templates from storage of some sort (e.g. filesystem, database).
+#
+# This uses the TEMPLATE_LOADERS setting, which is a list of loaders to use.
+# Each loader is expected to have this interface:
+#
+# callable(name, dirs=[])
+#
+# name is the template name.
+# dirs is an optional list of directories to search instead of TEMPLATE_DIRS.
+#
+# The loader should return a tuple of (template_source, path). The path returned
+# might be shown to the user for debugging purposes, so it should identify where
+# the template was loaded from.
+#
+# Each loader should have an "is_usable" attribute set. This is a boolean that
+# specifies whether the loader can be used in this Python installation. Each
+# loader is responsible for setting this when it's initialized.
+#
+# For example, the eggs loader (which is capable of loading templates from
+# Python eggs) sets is_usable to False if the "pkg_resources" module isn't
+# installed, because pkg_resources is necessary to read eggs.
+
+from django.core.exceptions import ImproperlyConfigured
+from django.template import Origin, StringOrigin, Template, Context, TemplateDoesNotExist, add_to_builtins
+from django.conf.settings import TEMPLATE_LOADERS
+from django.conf import settings
+
+template_source_loaders = []
+for path in TEMPLATE_LOADERS:
+ i = path.rfind('.')
+ module, attr = path[:i], path[i+1:]
+ try:
+ mod = __import__(module, globals(), locals(), [attr])
+ except ImportError, e:
+ raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
+ try:
+ func = getattr(mod, attr)
+ except AttributeError:
+ raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable template source loader' % (module, attr)
+ if not func.is_usable:
+ import warnings
+ warnings.warn("Your TEMPLATE_LOADERS setting includes %r, but your Python installation doesn't support that type of template loading. Consider removing that line from TEMPLATE_LOADERS." % path)
+ else:
+ template_source_loaders.append(func)
+
+class LoaderOrigin(Origin):
+ def __init__(self, display_name, loader, name, dirs):
+ super(LoaderOrigin, self).__init__(display_name)
+ self.loader, self.loadname, self.dirs = loader, name, dirs
+
+ def reload(self):
+ return self.loader(self.loadname, self.dirs)[0]
+
+def make_origin(display_name, loader, name, dirs):
+ if settings.TEMPLATE_DEBUG:
+ return LoaderOrigin(display_name, loader, name, dirs)
+ else:
+ return None
+
+def find_template_source(name, dirs=None):
+ for loader in template_source_loaders:
+ try:
+ source, display_name = loader(name, dirs)
+ return (source, make_origin(display_name, loader, name, dirs))
+ except TemplateDoesNotExist:
+ pass
+ raise TemplateDoesNotExist, name
+
+def get_template(template_name):
+ """
+ Returns a compiled Template object for the given template name,
+ handling template inheritance recursively.
+ """
+ return get_template_from_string(*find_template_source(template_name))
+
+def get_template_from_string(source, origin=None ):
+ """
+ Returns a compiled Template object for the given template code,
+ handling template inheritance recursively.
+ """
+ return Template(source, origin)
+
+def render_to_string(template_name, dictionary=None, context_instance=None):
+ """
+ Loads the given template_name and renders it with the given dictionary as
+ context. The template_name may be a string to load a single template using
+ get_template, or it may be a tuple to use select_template to find one of
+ the templates in the list. Returns a string.
+ """
+ dictionary = dictionary or {}
+ if isinstance(template_name, (list, tuple)):
+ t = select_template(template_name)
+ else:
+ t = get_template(template_name)
+ if context_instance:
+ context_instance.update(dictionary)
+ else:
+ context_instance = Context(dictionary)
+ return t.render(context_instance)
+
+def select_template(template_name_list):
+ "Given a list of template names, returns the first that can be loaded."
+ for template_name in template_name_list:
+ try:
+ return get_template(template_name)
+ except TemplateDoesNotExist:
+ continue
+ # If we get here, none of the templates could be loaded
+ raise TemplateDoesNotExist, ', '.join(template_name_list)
+
+add_to_builtins('django.template.loader_tags')
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
new file mode 100644
index 0000000000..eafe902b82
--- /dev/null
+++ b/django/template/loader_tags.py
@@ -0,0 +1,172 @@
+from django.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable
+from django.template import Library, Context, Node
+from django.template.loader import get_template, get_template_from_string, find_template_source
+
+register = Library()
+
+class ExtendsError(Exception):
+ pass
+
+class BlockNode(Node):
+ def __init__(self, name, nodelist, parent=None):
+ self.name, self.nodelist, self.parent = name, nodelist, parent
+
+ def __repr__(self):
+ return "" % (self.name, self.nodelist)
+
+ def render(self, context):
+ context.push()
+ # Save context in case of block.super().
+ self.context = context
+ context['block'] = self
+ result = self.nodelist.render(context)
+ context.pop()
+ return result
+
+ def super(self):
+ if self.parent:
+ return self.parent.render(self.context)
+ return ''
+
+ def add_parent(self, nodelist):
+ if self.parent:
+ self.parent.add_parent(nodelist)
+ else:
+ self.parent = BlockNode(self.name, nodelist)
+
+class ExtendsNode(Node):
+ def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
+ self.nodelist = nodelist
+ self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
+ self.template_dirs = template_dirs
+
+ def get_parent(self, context):
+ if self.parent_name_expr:
+ self.parent_name = self.parent_name_expr.resolve(context)
+ parent = self.parent_name
+ if not parent:
+ error_msg = "Invalid template name in 'extends' tag: %r." % parent
+ if self.parent_name_expr:
+ error_msg += " Got this from the %r variable." % self.parent_name_expr #TODO nice repr.
+ raise TemplateSyntaxError, error_msg
+ try:
+ return get_template_from_string(*find_template_source(parent, self.template_dirs))
+ except TemplateDoesNotExist:
+ raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
+
+ def render(self, context):
+ compiled_parent = self.get_parent(context)
+ parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
+ parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
+ for block_node in self.nodelist.get_nodes_by_type(BlockNode):
+ # Check for a BlockNode with this node's name, and replace it if found.
+ try:
+ parent_block = parent_blocks[block_node.name]
+ except KeyError:
+ # This BlockNode wasn't found in the parent template, but the
+ # parent block might be defined in the parent's *parent*, so we
+ # add this BlockNode to the parent's ExtendsNode nodelist, so
+ # it'll be checked when the parent node's render() is called.
+ if parent_is_child:
+ compiled_parent.nodelist[0].nodelist.append(block_node)
+ else:
+ # Keep any existing parents and add a new one. Used by BlockNode.
+ parent_block.parent = block_node.parent
+ parent_block.add_parent(parent_block.nodelist)
+ parent_block.nodelist = block_node.nodelist
+ return compiled_parent.render(context)
+
+class ConstantIncludeNode(Node):
+ def __init__(self, template_path):
+ try:
+ t = get_template(template_path)
+ self.template = t
+ except:
+ from django.conf.settings import TEMPLATE_DEBUG
+ if TEMPLATE_DEBUG:
+ raise
+ self.template = None
+
+ def render(self, context):
+ if self.template:
+ return self.template.render(context)
+ else:
+ return ''
+
+class IncludeNode(Node):
+ def __init__(self, template_name):
+ self.template_name = template_name
+
+ def render(self, context):
+ try:
+ template_name = resolve_variable(self.template_name, context)
+ t = get_template(template_name)
+ return t.render(context)
+ except TemplateSyntaxError, e:
+ if TEMPLATE_DEBUG:
+ raise
+ return ''
+ except:
+ return '' # Fail silently for invalid included templates.
+
+def do_block(parser, token):
+ """
+ Define a block that can be overridden by child templates.
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
+ block_name = bits[1]
+ # Keep track of the names of BlockNodes found in this template, so we can
+ # check for duplication.
+ try:
+ if block_name in parser.__loaded_blocks:
+ raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
+ parser.__loaded_blocks.append(block_name)
+ except AttributeError: # parser._loaded_blocks isn't a list yet
+ parser.__loaded_blocks = [block_name]
+ nodelist = parser.parse(('endblock',))
+ parser.delete_first_token()
+ return BlockNode(block_name, nodelist)
+
+def do_extends(parser, token):
+ """
+ Signal that this template extends a parent template.
+
+ This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
+ uses the literal value "base" as the name of the parent template to extend,
+ or ``{% extends variable %}`` uses the value of ``variable`` as the name
+ of the parent template to extend.
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
+ parent_name, parent_name_expr = None, None
+ if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
+ parent_name = bits[1][1:-1]
+ else:
+ parent_name_expr = parser.compile_filter(bits[1])
+ nodelist = parser.parse()
+ if nodelist.get_nodes_by_type(ExtendsNode):
+ raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
+ return ExtendsNode(nodelist, parent_name, parent_name_expr)
+
+def do_include(parser, token):
+ """
+ Loads a template and renders it with the current context.
+
+ Example::
+
+ {% include "foo/some_include" %}
+ """
+ bits = token.contents.split()
+ if len(bits) != 2:
+ raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
+ path = bits[1]
+ if path[0] in ('"', "'") and path[-1] == path[0]:
+ return ConstantIncludeNode(path[1:-1])
+ return IncludeNode(bits[1])
+
+register.tag('block', do_block)
+register.tag('extends', do_extends)
+register.tag('include', do_include)
diff --git a/django/template/loaders/__init__.py b/django/template/loaders/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/template/loaders/app_directories.py b/django/template/loaders/app_directories.py
new file mode 100644
index 0000000000..1d4dc6bef7
--- /dev/null
+++ b/django/template/loaders/app_directories.py
@@ -0,0 +1,41 @@
+# Wrapper for loading templates from "template" directories in installed app packages.
+
+from django.conf.settings import INSTALLED_APPS, TEMPLATE_FILE_EXTENSION
+from django.core.exceptions import ImproperlyConfigured
+from django.template import TemplateDoesNotExist
+import os
+
+# At compile time, cache the directories to search.
+app_template_dirs = []
+for app in INSTALLED_APPS:
+ i = app.rfind('.')
+ if i == -1:
+ m, a = app, None
+ else:
+ m, a = app[:i], app[i+1:]
+ try:
+ if a is None:
+ mod = __import__(m, '', '', [])
+ else:
+ mod = getattr(__import__(m, '', '', [a]), a)
+ except ImportError, e:
+ raise ImproperlyConfigured, 'ImportError %s: %s' % (app, e.args[0])
+ template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates')
+ if os.path.isdir(template_dir):
+ app_template_dirs.append(template_dir)
+
+# It won't change, so convert it to a tuple to save memory.
+app_template_dirs = tuple(app_template_dirs)
+
+def get_template_sources(template_name, template_dirs=None):
+ for template_dir in app_template_dirs:
+ yield os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
+
+def load_template_source(template_name, template_dirs=None):
+ for filepath in get_template_sources(template_name, template_dirs):
+ try:
+ return (open(filepath).read(), filepath)
+ except IOError:
+ pass
+ raise TemplateDoesNotExist, template_name
+load_template_source.is_usable = True
diff --git a/django/template/loaders/eggs.py b/django/template/loaders/eggs.py
new file mode 100644
index 0000000000..62e4d4db33
--- /dev/null
+++ b/django/template/loaders/eggs.py
@@ -0,0 +1,25 @@
+# Wrapper for loading templates from eggs via pkg_resources.resource_string.
+
+try:
+ from pkg_resources import resource_string
+except ImportError:
+ resource_string = None
+
+from django.template import TemplateDoesNotExist
+from django.conf.settings import INSTALLED_APPS, TEMPLATE_FILE_EXTENSION
+
+def load_template_source(template_name, template_dirs=None):
+ """
+ Loads templates from Python eggs via pkg_resource.resource_string.
+
+ For every installed app, it tries to get the resource (app, template_name).
+ """
+ if resource_string is not None:
+ pkg_name = 'templates/' + template_name + TEMPLATE_FILE_EXTENSION
+ for app in INSTALLED_APPS:
+ try:
+ return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
+ except:
+ pass
+ raise TemplateDoesNotExist, template_name
+load_template_source.is_usable = resource_string is not None
diff --git a/django/template/loaders/filesystem.py b/django/template/loaders/filesystem.py
new file mode 100644
index 0000000000..4abe50d764
--- /dev/null
+++ b/django/template/loaders/filesystem.py
@@ -0,0 +1,25 @@
+# Wrapper for loading templates from the filesystem.
+
+from django.conf.settings import TEMPLATE_DIRS, TEMPLATE_FILE_EXTENSION
+from django.template import TemplateDoesNotExist
+import os
+
+def get_template_sources(template_name, template_dirs=None):
+ if not template_dirs:
+ template_dirs = TEMPLATE_DIRS
+ for template_dir in template_dirs:
+ yield os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
+
+def load_template_source(template_name, template_dirs=None):
+ tried = []
+ for filepath in get_template_sources(template_name, template_dirs):
+ try:
+ return (open(filepath).read(), filepath)
+ except IOError:
+ tried.append(filepath)
+ if template_dirs:
+ error_msg = "Tried %s" % tried
+ else:
+ error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory."
+ raise TemplateDoesNotExist, error_msg
+load_template_source.is_usable = True
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index 7c2019cac0..2e96478f46 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -1,6 +1,6 @@
-from django.core.template import Node, NodeList, Template, Context, resolve_variable
-from django.core.template import TemplateSyntaxError, TokenParser, Library
-from django.core.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR
+from django.template import Node, NodeList, Template, Context, resolve_variable
+from django.template import TemplateSyntaxError, TokenParser, Library
+from django.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR
from django.utils import translation
import re, sys
diff --git a/django/utils/translation.py b/django/utils/translation.py
index 9a924385d6..4eb7f6c7bc 100644
--- a/django/utils/translation.py
+++ b/django/utils/translation.py
@@ -384,7 +384,7 @@ def templateize(src):
does so by translating the Django translation tags into standard gettext
function invocations.
"""
- from django.core.template import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK
+ from django.template import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK
out = StringIO()
intrans = False
inplural = False
diff --git a/django/views/debug.py b/django/views/debug.py
index 70978a5128..09ae11477a 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -1,5 +1,5 @@
from django.conf import settings
-from django.core.template import Template, Context, TemplateDoesNotExist
+from django.template import Template, Context, TemplateDoesNotExist
from django.utils.html import escape
from django.http import HttpResponseServerError, HttpResponseNotFound
import inspect, os, re, sys
@@ -72,7 +72,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
template_does_not_exist = False
loader_debug_info = None
if issubclass(exc_type, TemplateDoesNotExist):
- from django.core.template.loader import template_source_loaders
+ from django.template.loader import template_source_loaders
template_does_not_exist = True
loader_debug_info = []
for loader in template_source_loaders:
diff --git a/django/views/defaults.py b/django/views/defaults.py
index 5287963d0f..953cad9122 100644
--- a/django/views/defaults.py
+++ b/django/views/defaults.py
@@ -1,5 +1,5 @@
from django.core.exceptions import ObjectDoesNotExist
-from django.core.template import Context, loader
+from django.template import Context, loader
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django import http
diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
index dab95bbca2..c4c6084ac8 100644
--- a/django/views/generic/create_update.py
+++ b/django/views/generic/create_update.py
@@ -1,5 +1,6 @@
+from django import models
from django.core.xheaders import populate_xheaders
-from django.core.template import loader
+from django.template import loader
from django.core import formfields, meta
from django.views.auth.login import redirect_to_login
from django.core.extensions import DjangoContext
@@ -7,13 +8,13 @@ from django.core.paginator import ObjectPaginator, InvalidPage
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
-def create_object(request, model, template_name=None,
+def create_object(request, app_label, module_name, template_name=None,
template_loader=loader, extra_context={}, post_save_redirect=None,
login_required=False, follow=None, context_processors=None):
"""
Generic object-creation function.
- Templates: ``/_form``
+ Templates: ``/_form``
Context:
form
the form wrapper for the object
@@ -21,12 +22,13 @@ def create_object(request, model, template_name=None,
if login_required and request.user.is_anonymous():
return redirect_to_login(request.path)
- manipulator = model.AddManipulator(follow=follow)
+ mod = models.get_module(app_label, module_name)
+ manipulator = mod.AddManipulator(follow=follow)
if request.POST:
# If data was POSTed, we're trying to create a new object
new_data = request.POST.copy()
- if model._meta.has_field_type(meta.FileField):
+ if mod.Klass._meta.has_field_type(meta.FileField):
new_data.update(request.FILES)
# Check for errors
@@ -38,7 +40,7 @@ def create_object(request, model, template_name=None,
new_object = manipulator.save(new_data)
if not request.user.is_anonymous():
- request.user.add_message("The %s was created sucessfully." % model._meta.verbose_name)
+ request.user.add_message("The %s was created sucessfully." % mod.Klass._meta.verbose_name)
# Redirect to the new object: first by trying post_save_redirect,
# then by obj.get_absolute_url; fail if neither works.
@@ -56,7 +58,7 @@ def create_object(request, model, template_name=None,
# Create the FormWrapper, template, context, response
form = formfields.FormWrapper(manipulator, new_data, errors)
if not template_name:
- template_name = "%s/%s_form" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_form" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = DjangoContext(request, {
'form': form,
@@ -68,14 +70,14 @@ def create_object(request, model, template_name=None,
c[key] = value
return HttpResponse(t.render(c))
-def update_object(request, model, object_id=None, slug=None,
+def update_object(request, app_label, module_name, object_id=None, slug=None,
slug_field=None, template_name=None, template_loader=loader,
extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None,
login_required=False, follow=None, context_processors=None):
"""
Generic object-update function.
- Templates: ``/_form``
+ Templates: ``/_form``
Context:
form
the form wrapper for the object
@@ -85,21 +87,23 @@ def update_object(request, model, object_id=None, slug=None,
if login_required and request.user.is_anonymous():
return redirect_to_login(request.path)
+ mod = models.get_module(app_label, module_name)
+
# Look up the object to be edited
lookup_kwargs = {}
if object_id:
- lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+ lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = object_id
elif slug and slug_field:
lookup_kwargs['%s__exact' % slug_field] = slug
else:
raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
lookup_kwargs.update(extra_lookup_kwargs)
try:
- object = model._default_manager.get_object(**lookup_kwargs)
+ object = mod.get_object(**lookup_kwargs)
except ObjectDoesNotExist:
- raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
+ raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
- manipulator = model.ChangeManipulator(object.id, follow=follow)
+ manipulator = mod.ChangeManipulator(object.id, follow=follow)
if request.POST:
new_data = request.POST.copy()
@@ -109,7 +113,7 @@ def update_object(request, model, object_id=None, slug=None,
manipulator.save(new_data)
if not request.user.is_anonymous():
- request.user.add_message("The %s was updated sucessfully." % model._meta.verbose_name)
+ request.user.add_message("The %s was updated sucessfully." % mod.Klass._meta.verbose_name)
# Do a post-after-redirect so that reload works, etc.
if post_save_redirect:
@@ -125,7 +129,7 @@ def update_object(request, model, object_id=None, slug=None,
form = formfields.FormWrapper(manipulator, new_data, errors)
if not template_name:
- template_name = "%s/%s_form" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_form" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = DjangoContext(request, {
'form': form,
@@ -137,10 +141,10 @@ def update_object(request, model, object_id=None, slug=None,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, model, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
return response
-def delete_object(request, model, post_delete_redirect,
+def delete_object(request, app_label, module_name, post_delete_redirect,
object_id=None, slug=None, slug_field=None, template_name=None,
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
login_required=False, context_processors=None):
@@ -151,7 +155,7 @@ def delete_object(request, model, post_delete_redirect,
fetched using GET; for safty, deletion will only be performed if this
view is POSTed.
- Templates: ``/_confirm_delete``
+ Templates: ``/_confirm_delete``
Context:
object
the original object being deleted
@@ -159,28 +163,30 @@ def delete_object(request, model, post_delete_redirect,
if login_required and request.user.is_anonymous():
return redirect_to_login(request.path)
+ mod = models.get_module(app_label, module_name)
+
# Look up the object to be edited
lookup_kwargs = {}
if object_id:
- lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+ lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = object_id
elif slug and slug_field:
lookup_kwargs['%s__exact' % slug_field] = slug
else:
raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field")
lookup_kwargs.update(extra_lookup_kwargs)
try:
- object = model._default_manager.get_object(**lookup_kwargs)
+ object = mod.get_object(**lookup_kwargs)
except ObjectDoesNotExist:
- raise Http404, "No %s found for %s" % (model._meta.app_label, lookup_kwargs)
+ raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
if request.META['REQUEST_METHOD'] == 'POST':
object.delete()
if not request.user.is_anonymous():
- request.user.add_message("The %s was deleted." % model._meta.verbose_name)
+ request.user.add_message("The %s was deleted." % mod.Klass._meta.verbose_name)
return HttpResponseRedirect(post_delete_redirect)
else:
if not template_name:
- template_name = "%s/%s_confirm_delete" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_confirm_delete" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = DjangoContext(request, {
'object': object,
@@ -191,5 +197,5 @@ def delete_object(request, model, post_delete_redirect,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, model, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
return response
diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
index 71a4880c92..86857370bd 100644
--- a/django/views/generic/date_based.py
+++ b/django/views/generic/date_based.py
@@ -1,40 +1,42 @@
-from django.core.template import loader
+from django.template import loader
from django.core.exceptions import ObjectDoesNotExist
from django.core.extensions import DjangoContext
from django.core.xheaders import populate_xheaders
+from django.models import get_module
from django.http import Http404, HttpResponse
import datetime, time
-def archive_index(request, model, date_field, num_latest=15,
+def archive_index(request, app_label, module_name, date_field, num_latest=15,
template_name=None, template_loader=loader, extra_lookup_kwargs={},
extra_context={}, allow_empty=False, context_processors=None):
"""
Generic top-level archive of date-based objects.
- Templates: ``/_archive``
+ Templates: ``/_archive``
Context:
date_list
List of years
latest
Latest N (defaults to 15) objects by date
"""
+ mod = get_module(app_label, module_name)
lookup_kwargs = {'%s__lte' % date_field: datetime.datetime.now()}
lookup_kwargs.update(extra_lookup_kwargs)
- date_list = getattr(model._default_manager, "get_%s_list" % date_field)('year', **lookup_kwargs)[::-1]
+ date_list = getattr(mod, "get_%s_list" % date_field)('year', **lookup_kwargs)[::-1]
if not date_list and not allow_empty:
- raise Http404, "No %s available" % model._meta.verbose_name
+ raise Http404("No %s.%s available" % (app_label, module_name))
if date_list and num_latest:
lookup_kwargs.update({
'limit': num_latest,
'order_by': ('-' + date_field,),
})
- latest = model._default_manager.get_list(**lookup_kwargs)
+ latest = mod.get_list(**lookup_kwargs)
else:
latest = None
if not template_name:
- template_name = "%s/%s_archive" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_archive" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = DjangoContext(request, {
'date_list' : date_list,
@@ -47,30 +49,31 @@ def archive_index(request, model, date_field, num_latest=15,
c[key] = value
return HttpResponse(t.render(c))
-def archive_year(request, year, model, date_field,
+def archive_year(request, year, app_label, module_name, date_field,
template_name=None, template_loader=loader, extra_lookup_kwargs={},
extra_context={}, context_processors=None):
"""
Generic yearly archive view.
- Templates: ``/_archive_year``
+ Templates: ``/_archive_year``
Context:
date_list
List of months in this year with objects
year
This year
"""
+ mod = get_module(app_label, module_name)
now = datetime.datetime.now()
lookup_kwargs = {'%s__year' % date_field: year}
# Only bother to check current date if the year isn't in the past.
if int(year) >= now.year:
lookup_kwargs['%s__lte' % date_field] = now
lookup_kwargs.update(extra_lookup_kwargs)
- date_list = getattr(model._default_manager, "get_%s_list" % date_field)('month', **lookup_kwargs)
+ date_list = getattr(mod, "get_%s_list" % date_field)('month', **lookup_kwargs)
if not date_list:
raise Http404
if not template_name:
- template_name = "%s/%s_archive_year" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_archive_year" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = DjangoContext(request, {
'date_list': date_list,
@@ -83,13 +86,13 @@ def archive_year(request, year, model, date_field,
c[key] = value
return HttpResponse(t.render(c))
-def archive_month(request, year, month, model, date_field,
+def archive_month(request, year, month, app_label, module_name, date_field,
month_format='%b', template_name=None, template_loader=loader,
extra_lookup_kwargs={}, extra_context={}, context_processors=None):
"""
Generic monthly archive view.
- Templates: ``/_archive_month``
+ Templates: ``/_archive_month``
Context:
month:
this month
@@ -101,6 +104,7 @@ def archive_month(request, year, month, model, date_field,
except ValueError:
raise Http404
+ mod = get_module(app_label, module_name)
now = datetime.datetime.now()
# Calculate first and last day of month, for use in a date-range lookup.
first_day = date.replace(day=1)
@@ -113,11 +117,11 @@ def archive_month(request, year, month, model, date_field,
if last_day >= now.date():
lookup_kwargs['%s__lte' % date_field] = now
lookup_kwargs.update(extra_lookup_kwargs)
- object_list = model._default_manager.get_list(**lookup_kwargs)
+ object_list = mod.get_list(**lookup_kwargs)
if not object_list:
raise Http404
if not template_name:
- template_name = "%s/%s_archive_month" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_archive_month" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = DjangoContext(request, {
'object_list': object_list,
@@ -130,14 +134,14 @@ def archive_month(request, year, month, model, date_field,
c[key] = value
return HttpResponse(t.render(c))
-def archive_day(request, year, month, day, model, date_field,
+def archive_day(request, year, month, day, app_label, module_name, date_field,
month_format='%b', day_format='%d', template_name=None,
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
allow_empty=False, context_processors=None):
"""
Generic daily archive view.
- Templates: ``/_archive_day``
+ Templates: ``/_archive_day``
Context:
object_list:
list of objects published that day
@@ -153,6 +157,7 @@ def archive_day(request, year, month, day, model, date_field,
except ValueError:
raise Http404
+ mod = get_module(app_label, module_name)
now = datetime.datetime.now()
lookup_kwargs = {
'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
@@ -161,11 +166,11 @@ def archive_day(request, year, month, day, model, date_field,
if date >= now.date():
lookup_kwargs['%s__lte' % date_field] = now
lookup_kwargs.update(extra_lookup_kwargs)
- object_list = model._default_manager.get_list(**lookup_kwargs)
+ object_list = mod.get_list(**lookup_kwargs)
if not allow_empty and not object_list:
raise Http404
if not template_name:
- template_name = "%s/%s_archive_day" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_archive_day" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = DjangoContext(request, {
'object_list': object_list,
@@ -192,7 +197,7 @@ def archive_today(request, **kwargs):
})
return archive_day(request, **kwargs)
-def object_detail(request, year, month, day, model, date_field,
+def object_detail(request, year, month, day, app_label, module_name, date_field,
month_format='%b', day_format='%d', object_id=None, slug=None,
slug_field=None, template_name=None, template_name_field=None,
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
@@ -200,7 +205,7 @@ def object_detail(request, year, month, day, model, date_field,
"""
Generic detail view from year/month/day/slug or year/month/day/id structure.
- Templates: ``/_detail``
+ Templates: ``/_detail``
Context:
object:
the object to be detailed
@@ -210,6 +215,7 @@ def object_detail(request, year, month, day, model, date_field,
except ValueError:
raise Http404
+ mod = get_module(app_label, module_name)
now = datetime.datetime.now()
lookup_kwargs = {
'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
@@ -218,18 +224,18 @@ def object_detail(request, year, month, day, model, date_field,
if date >= now.date():
lookup_kwargs['%s__lte' % date_field] = now
if object_id:
- lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+ lookup_kwargs['%s__exact' % mod.Klass._meta.pk.name] = object_id
elif slug and slug_field:
lookup_kwargs['%s__exact' % slug_field] = slug
else:
- raise AttributeError, "Generic detail view must be called with either an object_id or a slug/slugfield"
+ raise AttributeError("Generic detail view must be called with either an object_id or a slug/slugfield")
lookup_kwargs.update(extra_lookup_kwargs)
try:
- object = model._default_manager.get_object(**lookup_kwargs)
+ object = mod.get_object(**lookup_kwargs)
except ObjectDoesNotExist:
- raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
+ raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
if not template_name:
- template_name = "%s/%s_detail" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_detail" % (app_label, module_name)
if template_name_field:
template_name_list = [getattr(object, template_name_field), template_name]
t = template_loader.select_template(template_name_list)
@@ -244,5 +250,5 @@ def object_detail(request, year, month, day, model, date_field,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, model, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
return response
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
index 50dc3b5cfc..3d4fc32f14 100644
--- a/django/views/generic/list_detail.py
+++ b/django/views/generic/list_detail.py
@@ -1,17 +1,18 @@
-from django.core.template import loader
+from django import models
+from django.template import loader
from django.http import Http404, HttpResponse
from django.core.xheaders import populate_xheaders
from django.core.extensions import DjangoContext
from django.core.paginator import ObjectPaginator, InvalidPage
from django.core.exceptions import ObjectDoesNotExist
-def object_list(request, model, paginate_by=None, allow_empty=False,
+def object_list(request, app_label, module_name, paginate_by=None, allow_empty=False,
template_name=None, template_loader=loader, extra_lookup_kwargs={},
extra_context={}, context_processors=None):
"""
Generic list of objects.
- Templates: ``/_list``
+ Templates: ``/_list``
Context:
object_list
list of objects
@@ -34,9 +35,10 @@ def object_list(request, model, paginate_by=None, allow_empty=False,
hits
number of objects, total
"""
+ mod = models.get_module(app_label, module_name)
lookup_kwargs = extra_lookup_kwargs.copy()
if paginate_by:
- paginator = ObjectPaginator(model, lookup_kwargs, paginate_by)
+ paginator = ObjectPaginator(mod, lookup_kwargs, paginate_by)
page = request.GET.get('page', 0)
try:
object_list = paginator.get_page(page)
@@ -59,7 +61,7 @@ def object_list(request, model, paginate_by=None, allow_empty=False,
'hits' : paginator.hits,
}, context_processors)
else:
- object_list = model._default_manager.get_list(**lookup_kwargs)
+ object_list = mod.get_list(**lookup_kwargs)
c = DjangoContext(request, {
'object_list': object_list,
'is_paginated': False
@@ -72,36 +74,37 @@ def object_list(request, model, paginate_by=None, allow_empty=False,
else:
c[key] = value
if not template_name:
- template_name = "%s/%s_list" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_list" % (app_label, module_name)
t = template_loader.get_template(template_name)
return HttpResponse(t.render(c))
-def object_detail(request, model, object_id=None, slug=None,
+def object_detail(request, app_label, module_name, object_id=None, slug=None,
slug_field=None, template_name=None, template_name_field=None,
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
context_processors=None):
"""
Generic list of objects.
- Templates: ``/_detail``
+ Templates: ``/_detail``
Context:
object
the object
"""
+ mod = models.get_module(app_label, module_name)
lookup_kwargs = {}
if object_id:
lookup_kwargs['pk'] = object_id
elif slug and slug_field:
lookup_kwargs['%s__exact' % slug_field] = slug
else:
- raise AttributeError, "Generic detail view must be called with either an object_id or a slug/slug_field."
+ raise AttributeError("Generic detail view must be called with either an object_id or a slug/slug_field")
lookup_kwargs.update(extra_lookup_kwargs)
try:
- object = model._default_manager.get_object(**lookup_kwargs)
+ object = mod.get_object(**lookup_kwargs)
except ObjectDoesNotExist:
- raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
+ raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
if not template_name:
- template_name = "%s/%s_detail" % (model._meta.app_label, model._meta.object_name.lower())
+ template_name = "%s/%s_detail" % (app_label, module_name)
if template_name_field:
template_name_list = [getattr(object, template_name_field), template_name]
t = template_loader.select_template(template_name_list)
@@ -116,5 +119,5 @@ def object_detail(request, model, object_id=None, slug=None,
else:
c[key] = value
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, model, getattr(object, object._meta.pk.name))
+ populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
return response
diff --git a/django/views/registration/passwords.py b/django/views/registration/passwords.py
index 162f64fc3a..ebdc06d1a9 100644
--- a/django/views/registration/passwords.py
+++ b/django/views/registration/passwords.py
@@ -1,6 +1,6 @@
from django.core import formfields, validators
from django.core.extensions import DjangoContext, render_to_response
-from django.core.template import Context, loader
+from django.template import Context, loader
from django.models.auth import User
from django.models.core import Site
from django.views.decorators.auth import login_required
diff --git a/django/views/static.py b/django/views/static.py
index cde95f4578..d0b24a2f42 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -5,7 +5,7 @@ import mimetypes
from django.core import template_loader
from django.core.exceptions import ImproperlyConfigured
from django.http import Http404, HttpResponse, HttpResponseRedirect
-from django.core.template import Template, Context, TemplateDoesNotExist
+from django.template import Template, Context, TemplateDoesNotExist
def serve(request, path, document_root=None, show_indexes=False):
"""