summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2008-01-09 06:08:40 +0000
committerMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2008-01-09 06:08:40 +0000
commit83cb2218bc73707f2c15a40caef97e375c4f1175 (patch)
tree395bab54ac4f6d3b65ef25d16fbba419fc704e5b /django
parentb824d803cef720fcf39ee19c54a14da811ea1c3d (diff)
queryset-refactor: Merged from trunk up to [7002].
git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7004 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
-rw-r--r--django/contrib/admin/media/js/urlify.js2
-rw-r--r--django/contrib/localflavor/ca/forms.py4
-rw-r--r--django/contrib/sessions/backends/db.py16
-rw-r--r--django/contrib/sessions/backends/file.py20
-rw-r--r--django/core/mail.py50
-rw-r--r--django/core/management/sql.py2
-rw-r--r--django/db/backends/oracle/base.py23
-rw-r--r--django/template/__init__.py171
-rw-r--r--django/template/context.py6
-rw-r--r--django/template/debug.py97
-rw-r--r--django/template/defaultfilters.py11
-rw-r--r--django/template/defaulttags.py44
-rw-r--r--django/views/debug.py2
13 files changed, 227 insertions, 221 deletions
diff --git a/django/contrib/admin/media/js/urlify.js b/django/contrib/admin/media/js/urlify.js
index 63e626d884..d8f2549e27 100644
--- a/django/contrib/admin/media/js/urlify.js
+++ b/django/contrib/admin/media/js/urlify.js
@@ -5,7 +5,7 @@ var LATIN_MAP = {
'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U',
'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä':
'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e',
- 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'o', 'ñ': 'n', 'ò': 'o', 'ó':
+ 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó':
'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u',
'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y'
}
diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py
index daeff58acf..b40dba8335 100644
--- a/django/contrib/localflavor/ca/forms.py
+++ b/django/contrib/localflavor/ca/forms.py
@@ -80,12 +80,12 @@ class CASocialInsuranceNumberField(Field):
Checks the following rules to determine whether the number is valid:
- * Conforms to the XXX-XXX-XXXX format.
+ * Conforms to the XXX-XXX-XXX format.
* Passes the check digit process "Luhn Algorithm"
See: http://en.wikipedia.org/wiki/Social_Insurance_Number
"""
default_error_messages = {
- 'invalid': ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.'),
+ 'invalid': ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'),
}
def clean(self, value):
diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py
index d1496d63bf..0f79d9ee1a 100644
--- a/django/contrib/sessions/backends/db.py
+++ b/django/contrib/sessions/backends/db.py
@@ -10,40 +10,42 @@ class SessionStore(SessionBase):
"""
def __init__(self, session_key=None):
super(SessionStore, self).__init__(session_key)
-
+
def load(self):
try:
s = Session.objects.get(
- session_key = self.session_key,
+ session_key = self.session_key,
expire_date__gt=datetime.datetime.now()
)
return self.decode(s.session_data)
except (Session.DoesNotExist, SuspiciousOperation):
-
+
# Create a new session_key for extra security.
self.session_key = self._get_new_session_key()
self._session_cache = {}
# Save immediately to minimize collision
self.save()
+ # Ensure the user is notified via a new cookie.
+ self.modified = True
return {}
-
+
def exists(self, session_key):
try:
Session.objects.get(session_key=session_key)
except Session.DoesNotExist:
return False
return True
-
+
def save(self):
Session.objects.create(
session_key = self.session_key,
session_data = self.encode(self._session),
expire_date = datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)
)
-
+
def delete(self, session_key):
try:
Session.objects.get(session_key=session_key).delete()
except Session.DoesNotExist:
- pass \ No newline at end of file
+ pass
diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py
index a8c3c69b10..cd3e3d9c75 100644
--- a/django/contrib/sessions/backends/file.py
+++ b/django/contrib/sessions/backends/file.py
@@ -10,31 +10,31 @@ class SessionStore(SessionBase):
"""
def __init__(self, session_key=None):
self.storage_path = getattr(settings, "SESSION_FILE_PATH", tempfile.gettempdir())
-
+
# Make sure the storage path is valid.
if not os.path.isdir(self.storage_path):
raise ImproperlyConfigured("The session storage path %r doesn't exist. "\
"Please set your SESSION_FILE_PATH setting "\
"to an existing directory in which Django "\
"can store session data." % self.storage_path)
-
- self.file_prefix = settings.SESSION_COOKIE_NAME
+
+ self.file_prefix = settings.SESSION_COOKIE_NAME
super(SessionStore, self).__init__(session_key)
-
+
def _key_to_file(self, session_key=None):
"""
Get the file associated with this session key.
"""
if session_key is None:
session_key = self.session_key
-
+
# Make sure we're not vulnerable to directory traversal. Session keys
# should always be md5s, so they should never contain directory components.
if os.path.sep in session_key:
raise SuspiciousOperation("Invalid characters (directory components) in session key")
-
+
return os.path.join(self.storage_path, self.file_prefix + session_key)
-
+
def load(self):
session_data = {}
try:
@@ -46,6 +46,8 @@ class SessionStore(SessionBase):
self._session_key = self._get_new_session_key()
self._session_cache = {}
self.save()
+ # Ensure the user is notified via a new cookie.
+ self.modified = True
finally:
session_file.close()
except(IOError):
@@ -66,12 +68,12 @@ class SessionStore(SessionBase):
if os.path.exists(self._key_to_file(session_key)):
return True
return False
-
+
def delete(self, session_key):
try:
os.unlink(self._key_to_file(session_key))
except OSError:
pass
-
+
def clean(self):
pass
diff --git a/django/core/mail.py b/django/core/mail.py
index e408393d96..153dcb6e63 100644
--- a/django/core/mail.py
+++ b/django/core/mail.py
@@ -67,42 +67,32 @@ def make_msgid(idstring=None):
class BadHeaderError(ValueError):
pass
+def forbid_multi_line_headers(name, val):
+ "Forbids multi-line headers, to prevent header injection."
+ if '\n' in val or '\r' in val:
+ raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
+ try:
+ val = force_unicode(val).encode('ascii')
+ except UnicodeEncodeError:
+ if name.lower() in ('to', 'from', 'cc'):
+ result = []
+ for item in val.split(', '):
+ nm, addr = parseaddr(item)
+ nm = str(Header(nm, settings.DEFAULT_CHARSET))
+ result.append(formataddr((nm, str(addr))))
+ val = ', '.join(result)
+ else:
+ val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
+ return name, val
+
class SafeMIMEText(MIMEText):
def __setitem__(self, name, val):
- "Forbids multi-line headers, to prevent header injection."
- if '\n' in val or '\r' in val:
- raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
- try:
- val = force_unicode(val).encode('ascii')
- except UnicodeEncodeError:
- if name.lower() in ('to', 'from', 'cc'):
- result = []
- for item in val.split(', '):
- nm, addr = parseaddr(item)
- nm = str(Header(nm, settings.DEFAULT_CHARSET))
- result.append(formataddr((nm, str(addr))))
- val = ', '.join(result)
- else:
- val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
+ name, val = forbid_multi_line_headers(name, val)
MIMEText.__setitem__(self, name, val)
class SafeMIMEMultipart(MIMEMultipart):
def __setitem__(self, name, val):
- "Forbids multi-line headers, to prevent header injection."
- if '\n' in val or '\r' in val:
- raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
- try:
- val = force_unicode(val).encode('ascii')
- except UnicodeEncodeError:
- if name.lower() in ('to', 'from', 'cc'):
- result = []
- for item in val.split(', '):
- nm, addr = parseaddr(item)
- nm = str(Header(nm, settings.DEFAULT_CHARSET))
- result.append(formataddr((nm, str(addr))))
- val = ', '.join(result)
- else:
- val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
+ name, val = forbid_multi_line_headers(name, val)
MIMEMultipart.__setitem__(self, name, val)
class SMTPConnection(object):
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 767a2dbbfb..15bffce26b 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -293,7 +293,7 @@ def sql_model_create(model, style, known_models=set()):
style.SQL_KEYWORD('NULL'))
for field_constraints in opts.unique_together:
table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
- ", ".join([qn(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints]))
+ ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
for i, line in enumerate(table_output): # Combine and add commas.
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index cd830413fc..70f5688d1a 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -413,6 +413,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
return self.connection is not None
def _cursor(self, settings):
+ cursor = None
if not self._valid_connection():
if len(settings.DATABASE_HOST.strip()) == 0:
settings.DATABASE_HOST = 'localhost'
@@ -422,16 +423,25 @@ class DatabaseWrapper(BaseDatabaseWrapper):
else:
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
self.connection = Database.connect(conn_string, **self.options)
+ cursor = FormatStylePlaceholderCursor(self.connection)
+ # Set oracle date to ansi date format. This only needs to execute
+ # once when we create a new connection.
+ cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD' "
+ "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
try:
self.oracle_version = int(self.connection.version.split('.')[0])
except ValueError:
pass
- cursor = FormatStylePlaceholderCursor(self.connection)
+ try:
+ self.connection.stmtcachesize = 20
+ except:
+ # Django docs specify cx_Oracle version 4.3.1 or higher, but
+ # stmtcachesize is available only in 4.3.2 and up.
+ pass
+ if not cursor:
+ cursor = FormatStylePlaceholderCursor(self.connection)
# Default arraysize of 1 is highly sub-optimal.
cursor.arraysize = 100
- # Set oracle date to ansi date format.
- cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'")
- cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
return cursor
class FormatStylePlaceholderCursor(Database.Cursor):
@@ -506,7 +516,10 @@ class FormatStylePlaceholderCursor(Database.Cursor):
return Database.Cursor.executemany(self, query, new_param_list)
def fetchone(self):
- return to_unicode(Database.Cursor.fetchone(self))
+ row = Database.Cursor.fetchone(self)
+ if row is None:
+ return row
+ return tuple([to_unicode(e) for e in row])
def fetchmany(self, size=None):
if size is None:
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 9b1868cfce..750c48988d 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -154,15 +154,12 @@ class StringOrigin(Origin):
class Template(object):
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
- "Compilation stage"
try:
template_string = smart_unicode(template_string)
except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
- if settings.TEMPLATE_DEBUG and origin == None:
+ if settings.TEMPLATE_DEBUG and origin is 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)
self.name = name
@@ -177,13 +174,18 @@ class Template(object):
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())
+ if settings.TEMPLATE_DEBUG:
+ from debug import DebugLexer, DebugParser
+ lexer_class, parser_class = DebugLexer, DebugParser
+ else:
+ lexer_class, parser_class = Lexer, Parser
+ lexer = lexer_class(template_string, origin)
+ parser = parser_class(lexer.tokenize())
return parser.parse()
class Token(object):
def __init__(self, token_type, contents):
- "The token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT"
+ # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
self.token_type, self.contents = token_type, contents
def __str__(self):
@@ -200,7 +202,7 @@ class Lexer(object):
self.origin = origin
def tokenize(self):
- "Return a list of tokens from a given template_string"
+ "Return a list of tokens from a given template_string."
in_tag = False
result = []
for bit in tag_re.split(self.template_string):
@@ -226,30 +228,6 @@ class Lexer(object):
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"
- result, upto = [], 0
- for match in tag_re.finditer(self.template_string):
- start, end = match.span()
- if start > upto:
- result.append(self.create_token(self.template_string[upto:start], (upto, start), False))
- upto = start
- result.append(self.create_token(self.template_string[start:end], (start, end), True))
- upto = end
- last_bit = self.template_string[upto:]
- if last_bit:
- result.append(self.create_token(last_bit, (upto, upto + len(last_bit)), False))
- return result
-
- def create_token(self, token_string, source, in_tag):
- token = super(DebugLexer, self).create_token(token_string, in_tag)
- token.source = self.origin, source
- return token
-
class Parser(object):
def __init__(self, tokens):
self.tokens = tokens
@@ -319,17 +297,17 @@ class Parser(object):
def exit_command(self):
pass
- def error(self, token, msg ):
+ def error(self, token, msg):
return TemplateSyntaxError(msg)
def empty_variable(self, token):
- raise self.error( token, "Empty variable tag")
+ raise self.error(token, "Empty variable tag")
def empty_block_tag(self, token):
- raise self.error( token, "Empty block tag")
+ raise self.error(token, "Empty block tag")
def invalid_block_tag(self, token, command):
- raise self.error( token, "Invalid block tag: '%s'" % 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))
@@ -358,57 +336,7 @@ class Parser(object):
if filter_name in self.filters:
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)
+ raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
class TokenParser(object):
"""
@@ -426,7 +354,7 @@ class TokenParser(object):
def top(self):
"Overload this method to do the actual parsing and return the result."
- raise NotImplemented
+ raise NotImplementedError()
def more(self):
"Returns True if there is more stuff in the tag."
@@ -435,7 +363,7 @@ class TokenParser(object):
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"
+ raise TemplateSyntaxError("back called without some previous parsing")
self.pointer = self.backout.pop()
def tag(self):
@@ -443,7 +371,7 @@ class TokenParser(object):
subject = self.subject
i = self.pointer
if i >= len(subject):
- raise TemplateSyntaxError, "expected another tag, found end of string: %s" % 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
@@ -459,14 +387,14 @@ class TokenParser(object):
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
+ 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" % (i, subject)
+ raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
i += 1
res = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'):
@@ -483,7 +411,7 @@ class TokenParser(object):
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" % (i, subject)
+ raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
i += 1
s = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'):
@@ -542,8 +470,8 @@ class FilterExpression(object):
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:])
+ 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:
@@ -552,9 +480,9 @@ class FilterExpression(object):
var = '"%s"' % constant.replace(r'\"', '"')
upto = match.end()
if var == None:
- raise TemplateSyntaxError, "Could not find variable at start of %s" % token
+ 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
+ raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
else:
filter_name = match.group("filter_name")
args = []
@@ -570,7 +498,7 @@ class FilterExpression(object):
filters.append( (filter_func,args))
upto = match.end()
if upto != len(token):
- raise TemplateSyntaxError, "Could not parse the remainder: '%s' from '%s'" % (token[upto:], token)
+ raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
self.filters = filters
self.var = Variable(var)
@@ -627,7 +555,7 @@ class FilterExpression(object):
provided.pop(0)
except IndexError:
# Not enough
- raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
+ raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
# Defaults can be overridden.
defaults = defaults and list(defaults) or []
@@ -636,7 +564,7 @@ class FilterExpression(object):
defaults.pop(0)
except IndexError:
# Too many.
- raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
+ raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
return True
args_check = staticmethod(args_check)
@@ -816,22 +744,6 @@ class NodeList(list):
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, e:
- from sys import exc_info
- wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
- wrapped.source = node.source
- wrapped.exc_info = exc_info()
- raise wrapped
- return result
-
class TextNode(Node):
def __init__(self, s):
self.s = s
@@ -861,21 +773,6 @@ class VariableNode(Node):
else:
return force_unicode(output)
-class DebugVariableNode(VariableNode):
- def render(self, context):
- try:
- output = force_unicode(self.filter_expression.resolve(context))
- except TemplateSyntaxError, e:
- if not hasattr(e, 'source'):
- e.source = self.source
- raise
- except UnicodeDecodeError:
- return ''
- if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
- return escape(output)
- else:
- return output
-
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
"Returns a template.Node subclass."
bits = token.split_contents()[1:]
@@ -887,7 +784,7 @@ def generic_tag_compiler(params, defaults, name, node_class, parser, token):
message = "%s takes %s arguments" % (name, bmin)
else:
message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
- raise TemplateSyntaxError, message
+ raise TemplateSyntaxError(message)
return node_class(bits)
class Library(object):
@@ -913,7 +810,7 @@ class Library(object):
self.tags[name] = compile_function
return compile_function
else:
- raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
+ raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
def tag_function(self,func):
self.tags[getattr(func, "_decorated_function", func).__name__] = func
@@ -937,7 +834,7 @@ class Library(object):
self.filters[name] = filter_func
return filter_func
else:
- raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)
+ raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
def filter_function(self, func):
self.filters[getattr(func, "_decorated_function", func).__name__] = func
@@ -966,7 +863,7 @@ class Library(object):
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'"
+ 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):
@@ -1003,12 +900,12 @@ def get_library(module_name):
try:
mod = __import__(module_name, {}, {}, [''])
except ImportError, e:
- raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, 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
+ raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
return lib
def add_to_builtins(module_name):
diff --git a/django/template/context.py b/django/template/context.py
index 0e41a26618..6ba53f340b 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -9,7 +9,6 @@ class ContextPopException(Exception):
class Context(object):
"A stack container for variable context"
-
def __init__(self, dict_=None, autoescape=True):
dict_ = dict_ or {}
self.dicts = [dict_]
@@ -78,11 +77,11 @@ def get_standard_processors():
try:
mod = __import__(module, {}, {}, [attr])
except ImportError, e:
- raise ImproperlyConfigured, 'Error importing request processor module %s: "%s"' % (module, e)
+ raise ImproperlyConfigured('Error importing request processor module %s: "%s"' % (module, e))
try:
func = getattr(mod, attr)
except AttributeError:
- raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable request processor' % (module, attr)
+ raise ImproperlyConfigured('Module "%s" does not define a "%s" callable request processor' % (module, attr))
processors.append(func)
_standard_context_processors = tuple(processors)
return _standard_context_processors
@@ -102,4 +101,3 @@ class RequestContext(Context):
processors = tuple(processors)
for processor in get_standard_processors() + processors:
self.update(processor(request))
-
diff --git a/django/template/debug.py b/django/template/debug.py
new file mode 100644
index 0000000000..008059c28f
--- /dev/null
+++ b/django/template/debug.py
@@ -0,0 +1,97 @@
+from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
+from django.utils.encoding import force_unicode
+from django.utils.html import escape
+from django.utils.safestring import SafeData, EscapeData
+
+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"
+ result, upto = [], 0
+ for match in tag_re.finditer(self.template_string):
+ start, end = match.span()
+ if start > upto:
+ result.append(self.create_token(self.template_string[upto:start], (upto, start), False))
+ upto = start
+ result.append(self.create_token(self.template_string[start:end], (start, end), True))
+ upto = end
+ last_bit = self.template_string[upto:]
+ if last_bit:
+ result.append(self.create_token(last_bit, (upto, upto + len(last_bit)), False))
+ return result
+
+ def create_token(self, token_string, source, in_tag):
+ token = super(DebugLexer, self).create_token(token_string, in_tag)
+ token.source = self.origin, source
+ return token
+
+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
+
+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, e:
+ from sys import exc_info
+ wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
+ wrapped.source = node.source
+ wrapped.exc_info = exc_info()
+ raise wrapped
+ return result
+
+class DebugVariableNode(VariableNode):
+ def render(self, context):
+ try:
+ output = force_unicode(self.filter_expression.resolve(context))
+ except TemplateSyntaxError, e:
+ if not hasattr(e, 'source'):
+ e.source = self.source
+ raise
+ except UnicodeDecodeError:
+ return ''
+ if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
+ return escape(output)
+ else:
+ return output
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 5cee0ab244..ddd48146f2 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -433,7 +433,7 @@ def first(value):
return value[0]
except IndexError:
return u''
-first.is_safe = True
+first.is_safe = False
def join(value, arg):
"""Joins a list with a string, like Python's ``str.join(list)``."""
@@ -449,6 +449,14 @@ def join(value, arg):
return data
join.is_safe = True
+def last(value):
+ "Returns the last item in a list"
+ try:
+ return value[-1]
+ except IndexError:
+ return u''
+last.is_safe = True
+
def length(value):
"""Returns the length of the value - useful for lists."""
return len(value)
@@ -800,6 +808,7 @@ register.filter(force_escape)
register.filter(get_digit)
register.filter(iriencode)
register.filter(join)
+register.filter(last)
register.filter(length)
register.filter(length_is)
register.filter(linebreaks)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 813e5dbed6..e5a8e6620b 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -84,19 +84,16 @@ class FirstOfNode(Node):
return u''
class ForNode(Node):
- def __init__(self, loopvars, sequence, reversed, nodelist_loop):
+ def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
self.loopvars, self.sequence = loopvars, sequence
- self.reversed = reversed
+ self.is_reversed = is_reversed
self.nodelist_loop = nodelist_loop
def __repr__(self):
- if self.reversed:
- reversed = ' reversed'
- else:
- reversed = ''
+ reversed_text = self.is_reversed and ' reversed' or ''
return "<For Node: for %s in %s, tail_len: %d%s>" % \
(', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
- reversed)
+ reversed_text)
def __iter__(self):
for node in self.nodelist_loop:
@@ -125,22 +122,23 @@ class ForNode(Node):
if not hasattr(values, '__len__'):
values = list(values)
len_values = len(values)
- if self.reversed:
+ if self.is_reversed:
values = reversed(values)
unpack = len(self.loopvars) > 1
+ # Create a forloop value in the context. We'll update counters on each
+ # iteration just below.
+ loop_dict = context['forloop'] = {'parentloop': parentloop}
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,
- }
+ # Shortcuts for current loop iteration number.
+ loop_dict['counter0'] = i
+ loop_dict['counter'] = i+1
+ # Reverse counter iteration numbers.
+ loop_dict['revcounter'] = len_values - i
+ loop_dict['revcounter0'] = len_values - i - 1
+ # Boolean values designating first and last times through loop.
+ loop_dict['first'] = (i == 0)
+ loop_dict['last'] = (i == len_values - 1)
+
if unpack:
# If there are multiple loop variables, unpack the item into
# them.
@@ -619,8 +617,8 @@ def do_for(parser, token):
raise TemplateSyntaxError("'for' statements should have at least four"
" words: %s" % token.contents)
- reversed = bits[-1] == 'reversed'
- in_index = reversed and -3 or -2
+ is_reversed = bits[-1] == 'reversed'
+ in_index = is_reversed and -3 or -2
if bits[in_index] != 'in':
raise TemplateSyntaxError("'for' statements should use the format"
" 'for x in y': %s" % token.contents)
@@ -634,7 +632,7 @@ def do_for(parser, token):
sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',))
parser.delete_first_token()
- return ForNode(loopvars, sequence, reversed, nodelist_loop)
+ return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
do_for = register.tag("for", do_for)
def do_ifequal(parser, token, negate):
diff --git a/django/views/debug.py b/django/views/debug.py
index 18a396d3a6..e3be2a7dcd 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -507,7 +507,7 @@ Exception Type: {{ exception_type|escape }} at {{ request.path|escape }}
Exception Value: {{ exception_value|escape }}
</textarea>
<br><br>
- <input type="submit" value="Share this traceback on public Web site">
+ <input type="submit" value="Share this traceback on a public Web site">
</div>
</form>
</div>