magic-removal: Moved django.core.template to django.template and updated dependencies.
git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1945 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
b6954b5ffc
commit
96116f60b4
|
@ -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.
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.core import template
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.core.template import Library
|
||||
from django.template import Library
|
||||
register = Library()
|
||||
|
||||
def admin_media_prefix():
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.core.template import Library
|
||||
from django.template import Library
|
||||
|
||||
register = Library()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.contrib.admin.models import LogEntry
|
||||
from django.core import template
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 = '''
|
||||
... <html>
|
||||
... {% if test %}
|
||||
... <h1>{{ varvalue }}</h1>
|
||||
... {% endif %}
|
||||
... </html>
|
||||
... '''
|
||||
>>> 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<html>\n\n <h1>Hello</h1>\n\n</html>\n'
|
||||
>>> c = template.Context({'test':False, 'varvalue': 'Hello'})
|
||||
>>> t.render(c)
|
||||
'\n<html>\n\n</html>\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<i18n_constant>%(str)s)"%(i18n_close)s|
|
||||
^"(?P<constant>%(str)s)"|
|
||||
^(?P<var>[%(var_chars)s]+)|
|
||||
(?:%(filter_sep)s
|
||||
(?P<filter_name>\w+)
|
||||
(?:%(arg_sep)s
|
||||
(?:
|
||||
%(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
|
||||
"(?P<constant_arg>%(str)s)"|
|
||||
(?P<var_arg>[%(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 "<Text Node: '%s'>" % 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 "<Variable Node: %s>" % 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')
|
|
@ -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 <p> and <br />s"
|
||||
from django.utils.html import linebreaks
|
||||
return linebreaks(value)
|
||||
|
||||
def linebreaksbr(value):
|
||||
"Converts newlines into <br />s"
|
||||
return value.replace('\n', '<br />')
|
||||
|
||||
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 <ul> 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::
|
||||
|
||||
<li>States
|
||||
<ul>
|
||||
<li>Kansas
|
||||
<ul>
|
||||
<li>Lawrence</li>
|
||||
<li>Topeka</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Illinois</li>
|
||||
</ul>
|
||||
</li>
|
||||
"""
|
||||
def _helper(value, tabs):
|
||||
indent = '\t' * tabs
|
||||
if value[1]:
|
||||
return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
|
||||
'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
|
||||
else:
|
||||
return '%s<li>%s</li>' % (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)
|
|
@ -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 "<For Node: for %s in %s, tail_len: %d%s>" % \
|
||||
(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 "<IfEqualNode>"
|
||||
|
||||
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 "<If node>"
|
||||
|
||||
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 %}
|
||||
<tr class="{% cycle row1,row2 %}">
|
||||
...
|
||||
</tr>
|
||||
{% 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::
|
||||
|
||||
<tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
|
||||
<tr class="{% cycle rowcolors %}">...</tr>
|
||||
<tr class="{% cycle rowcolors %}">...</tr>
|
||||
|
||||
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``::
|
||||
|
||||
<ul>
|
||||
{% for athlete in athlete_list %}
|
||||
<li>{{ athlete.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
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::
|
||||
|
||||
<h1>Archive for {{ year }}</h1>
|
||||
|
||||
{% for date in days %}
|
||||
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
|
||||
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
|
||||
{% 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 %}
|
||||
<ul>
|
||||
{% for group in grouped %}
|
||||
<li>{{ group.grouper }}
|
||||
<ul>
|
||||
{% for item in group.list %}
|
||||
<li>{{ item }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
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::
|
||||
|
||||
<img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
|
||||
|
||||
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)
|
|
@ -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')
|
|
@ -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 "<Block Node: %s. Contents: %r>" % (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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: ``<app_label>/<model_name>_form``
|
||||
Templates: ``<app_label>/<module_name>_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: ``<app_label>/<model_name>_form``
|
||||
Templates: ``<app_label>/<module_name>_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: ``<app_label>/<model_name>_confirm_delete``
|
||||
Templates: ``<app_label>/<module_name>_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
|
||||
|
|
|
@ -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: ``<app_label>/<model_name>_archive``
|
||||
Templates: ``<app_label>/<module_name>_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: ``<app_label>/<model_name>_archive_year``
|
||||
Templates: ``<app_label>/<module_name>_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: ``<app_label>/<model_name>_archive_month``
|
||||
Templates: ``<app_label>/<module_name>_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: ``<app_label>/<model_name>_archive_day``
|
||||
Templates: ``<app_label>/<module_name>_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: ``<app_label>/<model_name>_detail``
|
||||
Templates: ``<app_label>/<module_name>_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
|
||||
|
|
|
@ -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: ``<app_label>/<model_name>_list``
|
||||
Templates: ``<app_label>/<module_name>_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: ``<app_label>/<model_name>_detail``
|
||||
Templates: ``<app_label>/<module_name>_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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue