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
|
# See the comments in django/core/template/loader.py for interface
|
||||||
# documentation.
|
# documentation.
|
||||||
TEMPLATE_LOADERS = (
|
TEMPLATE_LOADERS = (
|
||||||
'django.core.template.loaders.filesystem.load_template_source',
|
'django.template.loaders.filesystem.load_template_source',
|
||||||
'django.core.template.loaders.app_directories.load_template_source',
|
'django.template.loaders.app_directories.load_template_source',
|
||||||
# 'django.core.template.loaders.eggs.load_template_source',
|
# 'django.template.loaders.eggs.load_template_source',
|
||||||
)
|
)
|
||||||
|
|
||||||
# List of processors used by DjangoContext to populate the context.
|
# 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.
|
# List of callables that know how to import templates from various sources.
|
||||||
TEMPLATE_LOADERS = (
|
TEMPLATE_LOADERS = (
|
||||||
'django.core.template.loaders.filesystem.load_template_source',
|
'django.template.loaders.filesystem.load_template_source',
|
||||||
'django.core.template.loaders.app_directories.load_template_source',
|
'django.template.loaders.app_directories.load_template_source',
|
||||||
# 'django.core.template.loaders.eggs.load_template_source',
|
# 'django.template.loaders.eggs.load_template_source',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
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 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 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.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.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import dateformat
|
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.text import capfirst
|
||||||
from django.utils.translation import get_date_formats
|
from django.utils.translation import get_date_formats
|
||||||
from django.conf.settings import ADMIN_MEDIA_PREFIX
|
from django.conf.settings import ADMIN_MEDIA_PREFIX
|
||||||
from django.core.template import Library
|
from django.template import Library
|
||||||
|
|
||||||
register = 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.html import escape
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.core import template
|
from django import template
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.core.template import Library
|
from django.template import Library
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
def admin_media_prefix():
|
def admin_media_prefix():
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.core.template import Library
|
from django.template import Library
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
from django.core import template
|
from django import template
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from django import templatetags
|
from django import template, templatetags
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.extensions import DjangoContext, render_to_response
|
from django.core.extensions import DjangoContext, render_to_response
|
||||||
from django.core.exceptions import ViewDoesNotExist
|
from django.core.exceptions import ViewDoesNotExist
|
||||||
from django.http import Http404
|
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.admin import utils
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
import inspect, os, re
|
import inspect, os, re
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# Generic admin views.
|
# Generic admin views.
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from django.core import formfields, template
|
from django.core import formfields
|
||||||
from django.core.template import loader
|
from django import template
|
||||||
|
from django.template import loader
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
|
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.decorators import staff_member_required
|
||||||
from django.contrib.admin.views.main import get_model_and_app
|
from django.contrib.admin.views.main import get_model_and_app
|
||||||
from django.contrib.admin.views.stages.modify import render_change_form
|
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.http import Http404
|
||||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
|
||||||
from django.core.extensions import DjangoContext as Context
|
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.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.http import Http404
|
||||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
|
||||||
from django.core.extensions import DjangoContext as Context
|
from django.core.extensions import DjangoContext as Context
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from django.core import formfields, validators
|
from django.core import formfields, validators
|
||||||
from django.core import template
|
from django import template
|
||||||
from django.core.template import loader
|
from django.template import loader
|
||||||
from django.core.extensions import DjangoContext, render_to_response
|
from django.core.extensions import DjangoContext, render_to_response
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib.comments.models import Comment, FreeComment
|
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 PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
|
||||||
from django.contrib.comments.models import MIN_PHOTO_DIMENSION, MAX_PHOTO_DIMENSION
|
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.core.exceptions import ObjectDoesNotExist
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
import re
|
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.
|
silently fail and return the un-marked-up text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core import template
|
from django import template
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
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.contrib.sites.models import Site
|
||||||
from django.utils import feedgenerator
|
from django.utils import feedgenerator
|
||||||
from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE
|
from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# for convenience's sake.
|
# for convenience's sake.
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
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.conf.settings import TEMPLATE_CONTEXT_PROCESSORS
|
||||||
from django.http import HttpResponse, Http404
|
from django.http import HttpResponse, Http404
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# This module is DEPRECATED!
|
# 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.template import Node, NodeList, Template, Context, resolve_variable
|
||||||
from django.core.template import TemplateSyntaxError, TokenParser, Library
|
from django.template import TemplateSyntaxError, TokenParser, Library
|
||||||
from django.core.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR
|
from django.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
import re, sys
|
import re, sys
|
||||||
|
|
||||||
|
|
|
@ -384,7 +384,7 @@ def templateize(src):
|
||||||
does so by translating the Django translation tags into standard gettext
|
does so by translating the Django translation tags into standard gettext
|
||||||
function invocations.
|
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()
|
out = StringIO()
|
||||||
intrans = False
|
intrans = False
|
||||||
inplural = False
|
inplural = False
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.conf import settings
|
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.utils.html import escape
|
||||||
from django.http import HttpResponseServerError, HttpResponseNotFound
|
from django.http import HttpResponseServerError, HttpResponseNotFound
|
||||||
import inspect, os, re, sys
|
import inspect, os, re, sys
|
||||||
|
@ -72,7 +72,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
|
||||||
template_does_not_exist = False
|
template_does_not_exist = False
|
||||||
loader_debug_info = None
|
loader_debug_info = None
|
||||||
if issubclass(exc_type, TemplateDoesNotExist):
|
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
|
template_does_not_exist = True
|
||||||
loader_debug_info = []
|
loader_debug_info = []
|
||||||
for loader in template_source_loaders:
|
for loader in template_source_loaders:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
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.contenttypes.models import ContentType
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django import http
|
from django import http
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
from django import models
|
||||||
from django.core.xheaders import populate_xheaders
|
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.core import formfields, meta
|
||||||
from django.views.auth.login import redirect_to_login
|
from django.views.auth.login import redirect_to_login
|
||||||
from django.core.extensions import DjangoContext
|
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.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
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,
|
template_loader=loader, extra_context={}, post_save_redirect=None,
|
||||||
login_required=False, follow=None, context_processors=None):
|
login_required=False, follow=None, context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic object-creation function.
|
Generic object-creation function.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_form``
|
Templates: ``<app_label>/<module_name>_form``
|
||||||
Context:
|
Context:
|
||||||
form
|
form
|
||||||
the form wrapper for the object
|
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():
|
if login_required and request.user.is_anonymous():
|
||||||
return redirect_to_login(request.path)
|
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 request.POST:
|
||||||
# If data was POSTed, we're trying to create a new object
|
# If data was POSTed, we're trying to create a new object
|
||||||
new_data = request.POST.copy()
|
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)
|
new_data.update(request.FILES)
|
||||||
|
|
||||||
# Check for errors
|
# Check for errors
|
||||||
|
@ -38,7 +40,7 @@ def create_object(request, model, template_name=None,
|
||||||
new_object = manipulator.save(new_data)
|
new_object = manipulator.save(new_data)
|
||||||
|
|
||||||
if not request.user.is_anonymous():
|
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,
|
# Redirect to the new object: first by trying post_save_redirect,
|
||||||
# then by obj.get_absolute_url; fail if neither works.
|
# 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
|
# Create the FormWrapper, template, context, response
|
||||||
form = formfields.FormWrapper(manipulator, new_data, errors)
|
form = formfields.FormWrapper(manipulator, new_data, errors)
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'form': form,
|
'form': form,
|
||||||
|
@ -68,14 +70,14 @@ def create_object(request, model, template_name=None,
|
||||||
c[key] = value
|
c[key] = value
|
||||||
return HttpResponse(t.render(c))
|
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,
|
slug_field=None, template_name=None, template_loader=loader,
|
||||||
extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None,
|
extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None,
|
||||||
login_required=False, follow=None, context_processors=None):
|
login_required=False, follow=None, context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic object-update function.
|
Generic object-update function.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_form``
|
Templates: ``<app_label>/<module_name>_form``
|
||||||
Context:
|
Context:
|
||||||
form
|
form
|
||||||
the form wrapper for the object
|
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():
|
if login_required and request.user.is_anonymous():
|
||||||
return redirect_to_login(request.path)
|
return redirect_to_login(request.path)
|
||||||
|
|
||||||
|
mod = models.get_module(app_label, module_name)
|
||||||
|
|
||||||
# Look up the object to be edited
|
# Look up the object to be edited
|
||||||
lookup_kwargs = {}
|
lookup_kwargs = {}
|
||||||
if object_id:
|
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:
|
elif slug and slug_field:
|
||||||
lookup_kwargs['%s__exact' % slug_field] = slug
|
lookup_kwargs['%s__exact' % slug_field] = slug
|
||||||
else:
|
else:
|
||||||
raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
|
raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
|
||||||
lookup_kwargs.update(extra_lookup_kwargs)
|
lookup_kwargs.update(extra_lookup_kwargs)
|
||||||
try:
|
try:
|
||||||
object = model._default_manager.get_object(**lookup_kwargs)
|
object = mod.get_object(**lookup_kwargs)
|
||||||
except ObjectDoesNotExist:
|
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:
|
if request.POST:
|
||||||
new_data = request.POST.copy()
|
new_data = request.POST.copy()
|
||||||
|
@ -109,7 +113,7 @@ def update_object(request, model, object_id=None, slug=None,
|
||||||
manipulator.save(new_data)
|
manipulator.save(new_data)
|
||||||
|
|
||||||
if not request.user.is_anonymous():
|
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.
|
# Do a post-after-redirect so that reload works, etc.
|
||||||
if post_save_redirect:
|
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)
|
form = formfields.FormWrapper(manipulator, new_data, errors)
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'form': form,
|
'form': form,
|
||||||
|
@ -137,10 +141,10 @@ def update_object(request, model, object_id=None, slug=None,
|
||||||
else:
|
else:
|
||||||
c[key] = value
|
c[key] = value
|
||||||
response = HttpResponse(t.render(c))
|
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
|
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,
|
object_id=None, slug=None, slug_field=None, template_name=None,
|
||||||
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
|
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
|
||||||
login_required=False, context_processors=None):
|
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
|
fetched using GET; for safty, deletion will only be performed if this
|
||||||
view is POSTed.
|
view is POSTed.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_confirm_delete``
|
Templates: ``<app_label>/<module_name>_confirm_delete``
|
||||||
Context:
|
Context:
|
||||||
object
|
object
|
||||||
the original object being deleted
|
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():
|
if login_required and request.user.is_anonymous():
|
||||||
return redirect_to_login(request.path)
|
return redirect_to_login(request.path)
|
||||||
|
|
||||||
|
mod = models.get_module(app_label, module_name)
|
||||||
|
|
||||||
# Look up the object to be edited
|
# Look up the object to be edited
|
||||||
lookup_kwargs = {}
|
lookup_kwargs = {}
|
||||||
if object_id:
|
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:
|
elif slug and slug_field:
|
||||||
lookup_kwargs['%s__exact' % slug_field] = slug
|
lookup_kwargs['%s__exact' % slug_field] = slug
|
||||||
else:
|
else:
|
||||||
raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field")
|
raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field")
|
||||||
lookup_kwargs.update(extra_lookup_kwargs)
|
lookup_kwargs.update(extra_lookup_kwargs)
|
||||||
try:
|
try:
|
||||||
object = model._default_manager.get_object(**lookup_kwargs)
|
object = mod.get_object(**lookup_kwargs)
|
||||||
except ObjectDoesNotExist:
|
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':
|
if request.META['REQUEST_METHOD'] == 'POST':
|
||||||
object.delete()
|
object.delete()
|
||||||
if not request.user.is_anonymous():
|
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)
|
return HttpResponseRedirect(post_delete_redirect)
|
||||||
else:
|
else:
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'object': object,
|
'object': object,
|
||||||
|
@ -191,5 +197,5 @@ def delete_object(request, model, post_delete_redirect,
|
||||||
else:
|
else:
|
||||||
c[key] = value
|
c[key] = value
|
||||||
response = HttpResponse(t.render(c))
|
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
|
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.exceptions import ObjectDoesNotExist
|
||||||
from django.core.extensions import DjangoContext
|
from django.core.extensions import DjangoContext
|
||||||
from django.core.xheaders import populate_xheaders
|
from django.core.xheaders import populate_xheaders
|
||||||
|
from django.models import get_module
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
import datetime, time
|
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={},
|
template_name=None, template_loader=loader, extra_lookup_kwargs={},
|
||||||
extra_context={}, allow_empty=False, context_processors=None):
|
extra_context={}, allow_empty=False, context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic top-level archive of date-based objects.
|
Generic top-level archive of date-based objects.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_archive``
|
Templates: ``<app_label>/<module_name>_archive``
|
||||||
Context:
|
Context:
|
||||||
date_list
|
date_list
|
||||||
List of years
|
List of years
|
||||||
latest
|
latest
|
||||||
Latest N (defaults to 15) objects by date
|
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 = {'%s__lte' % date_field: datetime.datetime.now()}
|
||||||
lookup_kwargs.update(extra_lookup_kwargs)
|
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:
|
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:
|
if date_list and num_latest:
|
||||||
lookup_kwargs.update({
|
lookup_kwargs.update({
|
||||||
'limit': num_latest,
|
'limit': num_latest,
|
||||||
'order_by': ('-' + date_field,),
|
'order_by': ('-' + date_field,),
|
||||||
})
|
})
|
||||||
latest = model._default_manager.get_list(**lookup_kwargs)
|
latest = mod.get_list(**lookup_kwargs)
|
||||||
else:
|
else:
|
||||||
latest = None
|
latest = None
|
||||||
|
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'date_list' : date_list,
|
'date_list' : date_list,
|
||||||
|
@ -47,30 +49,31 @@ def archive_index(request, model, date_field, num_latest=15,
|
||||||
c[key] = value
|
c[key] = value
|
||||||
return HttpResponse(t.render(c))
|
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={},
|
template_name=None, template_loader=loader, extra_lookup_kwargs={},
|
||||||
extra_context={}, context_processors=None):
|
extra_context={}, context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic yearly archive view.
|
Generic yearly archive view.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_archive_year``
|
Templates: ``<app_label>/<module_name>_archive_year``
|
||||||
Context:
|
Context:
|
||||||
date_list
|
date_list
|
||||||
List of months in this year with objects
|
List of months in this year with objects
|
||||||
year
|
year
|
||||||
This year
|
This year
|
||||||
"""
|
"""
|
||||||
|
mod = get_module(app_label, module_name)
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
lookup_kwargs = {'%s__year' % date_field: year}
|
lookup_kwargs = {'%s__year' % date_field: year}
|
||||||
# Only bother to check current date if the year isn't in the past.
|
# Only bother to check current date if the year isn't in the past.
|
||||||
if int(year) >= now.year:
|
if int(year) >= now.year:
|
||||||
lookup_kwargs['%s__lte' % date_field] = now
|
lookup_kwargs['%s__lte' % date_field] = now
|
||||||
lookup_kwargs.update(extra_lookup_kwargs)
|
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:
|
if not date_list:
|
||||||
raise Http404
|
raise Http404
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'date_list': date_list,
|
'date_list': date_list,
|
||||||
|
@ -83,13 +86,13 @@ def archive_year(request, year, model, date_field,
|
||||||
c[key] = value
|
c[key] = value
|
||||||
return HttpResponse(t.render(c))
|
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,
|
month_format='%b', template_name=None, template_loader=loader,
|
||||||
extra_lookup_kwargs={}, extra_context={}, context_processors=None):
|
extra_lookup_kwargs={}, extra_context={}, context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic monthly archive view.
|
Generic monthly archive view.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_archive_month``
|
Templates: ``<app_label>/<module_name>_archive_month``
|
||||||
Context:
|
Context:
|
||||||
month:
|
month:
|
||||||
this month
|
this month
|
||||||
|
@ -101,6 +104,7 @@ def archive_month(request, year, month, model, date_field,
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
mod = get_module(app_label, module_name)
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
# Calculate first and last day of month, for use in a date-range lookup.
|
# Calculate first and last day of month, for use in a date-range lookup.
|
||||||
first_day = date.replace(day=1)
|
first_day = date.replace(day=1)
|
||||||
|
@ -113,11 +117,11 @@ def archive_month(request, year, month, model, date_field,
|
||||||
if last_day >= now.date():
|
if last_day >= now.date():
|
||||||
lookup_kwargs['%s__lte' % date_field] = now
|
lookup_kwargs['%s__lte' % date_field] = now
|
||||||
lookup_kwargs.update(extra_lookup_kwargs)
|
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:
|
if not object_list:
|
||||||
raise Http404
|
raise Http404
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'object_list': object_list,
|
'object_list': object_list,
|
||||||
|
@ -130,14 +134,14 @@ def archive_month(request, year, month, model, date_field,
|
||||||
c[key] = value
|
c[key] = value
|
||||||
return HttpResponse(t.render(c))
|
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,
|
month_format='%b', day_format='%d', template_name=None,
|
||||||
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
|
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
|
||||||
allow_empty=False, context_processors=None):
|
allow_empty=False, context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic daily archive view.
|
Generic daily archive view.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_archive_day``
|
Templates: ``<app_label>/<module_name>_archive_day``
|
||||||
Context:
|
Context:
|
||||||
object_list:
|
object_list:
|
||||||
list of objects published that day
|
list of objects published that day
|
||||||
|
@ -153,6 +157,7 @@ def archive_day(request, year, month, day, model, date_field,
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
mod = get_module(app_label, module_name)
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
lookup_kwargs = {
|
lookup_kwargs = {
|
||||||
'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
|
'%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():
|
if date >= now.date():
|
||||||
lookup_kwargs['%s__lte' % date_field] = now
|
lookup_kwargs['%s__lte' % date_field] = now
|
||||||
lookup_kwargs.update(extra_lookup_kwargs)
|
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:
|
if not allow_empty and not object_list:
|
||||||
raise Http404
|
raise Http404
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'object_list': object_list,
|
'object_list': object_list,
|
||||||
|
@ -192,7 +197,7 @@ def archive_today(request, **kwargs):
|
||||||
})
|
})
|
||||||
return archive_day(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,
|
month_format='%b', day_format='%d', object_id=None, slug=None,
|
||||||
slug_field=None, template_name=None, template_name_field=None,
|
slug_field=None, template_name=None, template_name_field=None,
|
||||||
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
|
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.
|
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:
|
Context:
|
||||||
object:
|
object:
|
||||||
the object to be detailed
|
the object to be detailed
|
||||||
|
@ -210,6 +215,7 @@ def object_detail(request, year, month, day, model, date_field,
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
mod = get_module(app_label, module_name)
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
lookup_kwargs = {
|
lookup_kwargs = {
|
||||||
'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
|
'%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():
|
if date >= now.date():
|
||||||
lookup_kwargs['%s__lte' % date_field] = now
|
lookup_kwargs['%s__lte' % date_field] = now
|
||||||
if object_id:
|
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:
|
elif slug and slug_field:
|
||||||
lookup_kwargs['%s__exact' % slug_field] = slug
|
lookup_kwargs['%s__exact' % slug_field] = slug
|
||||||
else:
|
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)
|
lookup_kwargs.update(extra_lookup_kwargs)
|
||||||
try:
|
try:
|
||||||
object = model._default_manager.get_object(**lookup_kwargs)
|
object = mod.get_object(**lookup_kwargs)
|
||||||
except ObjectDoesNotExist:
|
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:
|
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:
|
if template_name_field:
|
||||||
template_name_list = [getattr(object, template_name_field), template_name]
|
template_name_list = [getattr(object, template_name_field), template_name]
|
||||||
t = template_loader.select_template(template_name_list)
|
t = template_loader.select_template(template_name_list)
|
||||||
|
@ -244,5 +250,5 @@ def object_detail(request, year, month, day, model, date_field,
|
||||||
else:
|
else:
|
||||||
c[key] = value
|
c[key] = value
|
||||||
response = HttpResponse(t.render(c))
|
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
|
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.http import Http404, HttpResponse
|
||||||
from django.core.xheaders import populate_xheaders
|
from django.core.xheaders import populate_xheaders
|
||||||
from django.core.extensions import DjangoContext
|
from django.core.extensions import DjangoContext
|
||||||
from django.core.paginator import ObjectPaginator, InvalidPage
|
from django.core.paginator import ObjectPaginator, InvalidPage
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
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={},
|
template_name=None, template_loader=loader, extra_lookup_kwargs={},
|
||||||
extra_context={}, context_processors=None):
|
extra_context={}, context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic list of objects.
|
Generic list of objects.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_list``
|
Templates: ``<app_label>/<module_name>_list``
|
||||||
Context:
|
Context:
|
||||||
object_list
|
object_list
|
||||||
list of objects
|
list of objects
|
||||||
|
@ -34,9 +35,10 @@ def object_list(request, model, paginate_by=None, allow_empty=False,
|
||||||
hits
|
hits
|
||||||
number of objects, total
|
number of objects, total
|
||||||
"""
|
"""
|
||||||
|
mod = models.get_module(app_label, module_name)
|
||||||
lookup_kwargs = extra_lookup_kwargs.copy()
|
lookup_kwargs = extra_lookup_kwargs.copy()
|
||||||
if paginate_by:
|
if paginate_by:
|
||||||
paginator = ObjectPaginator(model, lookup_kwargs, paginate_by)
|
paginator = ObjectPaginator(mod, lookup_kwargs, paginate_by)
|
||||||
page = request.GET.get('page', 0)
|
page = request.GET.get('page', 0)
|
||||||
try:
|
try:
|
||||||
object_list = paginator.get_page(page)
|
object_list = paginator.get_page(page)
|
||||||
|
@ -59,7 +61,7 @@ def object_list(request, model, paginate_by=None, allow_empty=False,
|
||||||
'hits' : paginator.hits,
|
'hits' : paginator.hits,
|
||||||
}, context_processors)
|
}, context_processors)
|
||||||
else:
|
else:
|
||||||
object_list = model._default_manager.get_list(**lookup_kwargs)
|
object_list = mod.get_list(**lookup_kwargs)
|
||||||
c = DjangoContext(request, {
|
c = DjangoContext(request, {
|
||||||
'object_list': object_list,
|
'object_list': object_list,
|
||||||
'is_paginated': False
|
'is_paginated': False
|
||||||
|
@ -72,36 +74,37 @@ def object_list(request, model, paginate_by=None, allow_empty=False,
|
||||||
else:
|
else:
|
||||||
c[key] = value
|
c[key] = value
|
||||||
if not template_name:
|
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)
|
t = template_loader.get_template(template_name)
|
||||||
return HttpResponse(t.render(c))
|
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,
|
slug_field=None, template_name=None, template_name_field=None,
|
||||||
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
|
template_loader=loader, extra_lookup_kwargs={}, extra_context={},
|
||||||
context_processors=None):
|
context_processors=None):
|
||||||
"""
|
"""
|
||||||
Generic list of objects.
|
Generic list of objects.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_detail``
|
Templates: ``<app_label>/<module_name>_detail``
|
||||||
Context:
|
Context:
|
||||||
object
|
object
|
||||||
the object
|
the object
|
||||||
"""
|
"""
|
||||||
|
mod = models.get_module(app_label, module_name)
|
||||||
lookup_kwargs = {}
|
lookup_kwargs = {}
|
||||||
if object_id:
|
if object_id:
|
||||||
lookup_kwargs['pk'] = object_id
|
lookup_kwargs['pk'] = object_id
|
||||||
elif slug and slug_field:
|
elif slug and slug_field:
|
||||||
lookup_kwargs['%s__exact' % slug_field] = slug
|
lookup_kwargs['%s__exact' % slug_field] = slug
|
||||||
else:
|
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)
|
lookup_kwargs.update(extra_lookup_kwargs)
|
||||||
try:
|
try:
|
||||||
object = model._default_manager.get_object(**lookup_kwargs)
|
object = mod.get_object(**lookup_kwargs)
|
||||||
except ObjectDoesNotExist:
|
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:
|
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:
|
if template_name_field:
|
||||||
template_name_list = [getattr(object, template_name_field), template_name]
|
template_name_list = [getattr(object, template_name_field), template_name]
|
||||||
t = template_loader.select_template(template_name_list)
|
t = template_loader.select_template(template_name_list)
|
||||||
|
@ -116,5 +119,5 @@ def object_detail(request, model, object_id=None, slug=None,
|
||||||
else:
|
else:
|
||||||
c[key] = value
|
c[key] = value
|
||||||
response = HttpResponse(t.render(c))
|
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
|
return response
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.core import formfields, validators
|
from django.core import formfields, validators
|
||||||
from django.core.extensions import DjangoContext, render_to_response
|
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.auth import User
|
||||||
from django.models.core import Site
|
from django.models.core import Site
|
||||||
from django.views.decorators.auth import login_required
|
from django.views.decorators.auth import login_required
|
||||||
|
|
|
@ -5,7 +5,7 @@ import mimetypes
|
||||||
from django.core import template_loader
|
from django.core import template_loader
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
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):
|
def serve(request, path, document_root=None, show_indexes=False):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue