mirror of
https://github.com/django/django.git
synced 2025-07-05 10:19:20 +00:00
Merged to r863. Fixes to template error reporting and som cleanup of field binding
git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@864 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
d41ffe4ee4
commit
a122c04d47
@ -2,9 +2,7 @@
|
|||||||
{% load admin_modify %}
|
{% load admin_modify %}
|
||||||
{% load adminmedia %}
|
{% load adminmedia %}
|
||||||
{% block extrahead %}
|
{% block extrahead %}
|
||||||
{% for js in javascript_imports %}
|
{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
|
||||||
{% include_admin_script js %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block coltype %}{{ coltype }}{% endblock %}
|
{% block coltype %}{{ coltype }}{% endblock %}
|
||||||
{% block bodyclass %}{{app_label}}-{{object_name.lower}} change-form{% endblock %}
|
{% block bodyclass %}{{app_label}}-{{object_name.lower}} change-form{% endblock %}
|
||||||
@ -15,36 +13,21 @@
|
|||||||
{% if add %}Add {{verbose_name}}{% else %}{{original|striptags|truncatewords:"18"}}{% endif %}
|
{% if add %}Add {{verbose_name}}{% else %}{{original|striptags|truncatewords:"18"}}{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}{% endblock %}
|
{% endif %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}<div id="content-main">
|
{% block content %}<div id="content-main">
|
||||||
{% if change %}
|
{% if change %}{% if not is_popup %}
|
||||||
{% if not is_popup %}
|
<ul class="object-tools"><li><a href="history/" class="historylink">History</a></li>
|
||||||
<ul class="object-tools"><li><a href="history/" class="historylink">History</a></li>
|
{% if has_absolute_url %}<li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>{% endif%}
|
||||||
{% if has_absolute_url %}
|
</ul>
|
||||||
<li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>
|
{% endif %}{% endif %}
|
||||||
{% endif%}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form {{ form_enc_attrib }} action='{{ form_url }}' method="post">
|
<form {{ form_enc_attrib }} action='{{ form_url }}' method="post">
|
||||||
|
|
||||||
{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
|
{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
|
||||||
|
{% if save_on_top %}{% submit_row %}{% endif %}
|
||||||
{% if save_on_top %}
|
{% if form.error_dict %}<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>{% endif %}
|
||||||
{% submit_row %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.error_dict %}
|
|
||||||
<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>
|
|
||||||
{% endif %}
|
|
||||||
<b>
|
<b>
|
||||||
</b>
|
</b>
|
||||||
{% for bound_field_set in bound_field_sets %}
|
{% for bound_field_set in bound_field_sets %}
|
||||||
<fieldset class="module aligned {{ bound_field_set.classes }}">
|
<fieldset class="module aligned {{ bound_field_set.classes }}">
|
||||||
{% if bound_field_set.name %}
|
{% if bound_field_set.name %}<h2>{{bound_field_set.name }}</h2>{% endif %}
|
||||||
<h2>{{bound_field_set.name }}</h2>
|
|
||||||
{% endif %}
|
|
||||||
{% for bound_field_line in bound_field_set %}
|
{% for bound_field_line in bound_field_set %}
|
||||||
{% admin_field_line bound_field_line %}
|
{% admin_field_line bound_field_line %}
|
||||||
{% for bound_field in bound_field_line %}
|
{% for bound_field in bound_field_line %}
|
||||||
@ -53,7 +36,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if change %}
|
{% if change %}
|
||||||
{% if ordered_objects %}
|
{% if ordered_objects %}
|
||||||
<fieldset class="module"><h2>Ordering</h2>
|
<fieldset class="module"><h2>Ordering</h2>
|
||||||
@ -63,11 +45,7 @@
|
|||||||
</div></fieldset>
|
</div></fieldset>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
|
||||||
|
|
||||||
{% for relation in inline_related_objects %}
|
|
||||||
{% edit_inline relation %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% submit_row %}
|
{% submit_row %}
|
||||||
|
|
||||||
@ -88,7 +66,7 @@
|
|||||||
<li id="p{% firstof ordered_object_names %}">
|
<li id="p{% firstof ordered_object_names %}">
|
||||||
<span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
|
<span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor%}
|
{% endfor%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif%}
|
{% endif%}
|
||||||
|
@ -49,7 +49,8 @@ DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', or 'sqlite3'.
|
|||||||
DATABASE_NAME = ''
|
DATABASE_NAME = ''
|
||||||
DATABASE_USER = ''
|
DATABASE_USER = ''
|
||||||
DATABASE_PASSWORD = ''
|
DATABASE_PASSWORD = ''
|
||||||
DATABASE_HOST = '' # Set to empty string for localhost
|
DATABASE_HOST = '' # Set to empty string for localhost.
|
||||||
|
DATABASE_PORT = '' # Set to empty string for default.
|
||||||
|
|
||||||
# Host for sending e-mail.
|
# Host for sending e-mail.
|
||||||
EMAIL_HOST = 'localhost'
|
EMAIL_HOST = 'localhost'
|
||||||
|
@ -15,6 +15,7 @@ DATABASE_NAME = '' # Or path to database file if using sqlite3.
|
|||||||
DATABASE_USER = '' # Not used with sqlite3.
|
DATABASE_USER = '' # Not used with sqlite3.
|
||||||
DATABASE_PASSWORD = '' # Not used with sqlite3.
|
DATABASE_PASSWORD = '' # Not used with sqlite3.
|
||||||
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
|
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
|
||||||
|
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
|
@ -53,10 +53,18 @@ class DatabaseWrapper:
|
|||||||
self.queries = []
|
self.queries = []
|
||||||
|
|
||||||
def cursor(self):
|
def cursor(self):
|
||||||
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG
|
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG
|
||||||
if self.connection is None:
|
if self.connection is None:
|
||||||
self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME,
|
kwargs = {
|
||||||
passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions)
|
'user': DATABASE_USER,
|
||||||
|
'db': DATABASE_NAME,
|
||||||
|
'passwd': DATABASE_PASSWORD,
|
||||||
|
'host': DATABASE_HOST,
|
||||||
|
'conv': django_conversions,
|
||||||
|
}
|
||||||
|
if DATABASE_PORT:
|
||||||
|
kwargs['port'] = DATABASE_PORT
|
||||||
|
self.connection = Database.connect(**kwargs)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
return base.CursorDebugWrapper(MysqlDebugWrapper(self.connection.cursor()), self)
|
return base.CursorDebugWrapper(MysqlDebugWrapper(self.connection.cursor()), self)
|
||||||
return self.connection.cursor()
|
return self.connection.cursor()
|
||||||
|
@ -15,7 +15,7 @@ class DatabaseWrapper:
|
|||||||
self.queries = []
|
self.queries = []
|
||||||
|
|
||||||
def cursor(self):
|
def cursor(self):
|
||||||
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PASSWORD, DEBUG, TIME_ZONE
|
from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG, TIME_ZONE
|
||||||
if self.connection is None:
|
if self.connection is None:
|
||||||
if DATABASE_NAME == '':
|
if DATABASE_NAME == '':
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
@ -27,6 +27,8 @@ class DatabaseWrapper:
|
|||||||
conn_string += " password=%s" % DATABASE_PASSWORD
|
conn_string += " password=%s" % DATABASE_PASSWORD
|
||||||
if DATABASE_HOST:
|
if DATABASE_HOST:
|
||||||
conn_string += " host=%s" % DATABASE_HOST
|
conn_string += " host=%s" % DATABASE_HOST
|
||||||
|
if DATABASE_PORT:
|
||||||
|
conn_string += " port=%s" % DATABASE_PORT
|
||||||
self.connection = Database.connect(conn_string)
|
self.connection = Database.connect(conn_string)
|
||||||
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
|
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
|
@ -333,6 +333,12 @@ def default(value, arg):
|
|||||||
"If value is unavailable, use given default"
|
"If value is unavailable, use given default"
|
||||||
return value or arg
|
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):
|
def divisibleby(value, arg):
|
||||||
"Returns true if the value is devisible by the argument"
|
"Returns true if the value is devisible by the argument"
|
||||||
return int(value) % int(arg) == 0
|
return int(value) % int(arg) == 0
|
||||||
|
@ -207,12 +207,18 @@ class RelatedObject(object):
|
|||||||
return [wrapping_func(f) for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field ]
|
return [wrapping_func(f) for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field ]
|
||||||
|
|
||||||
def get_follow(self, override=None):
|
def get_follow(self, override=None):
|
||||||
if override:
|
if isinstance(override, bool):
|
||||||
over = override.copy()
|
if override:
|
||||||
elif self.edit_inline:
|
over = {}
|
||||||
over = {}
|
else:
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
return None
|
if override:
|
||||||
|
over = override.copy()
|
||||||
|
elif self.edit_inline:
|
||||||
|
over = {}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
over[self.field.name] = False
|
over[self.field.name] = False
|
||||||
return self.opts.get_follow(over)
|
return self.opts.get_follow(over)
|
||||||
|
@ -804,7 +804,6 @@ class OneToOne(ManyToOne):
|
|||||||
self.raw_id_admin = raw_id_admin
|
self.raw_id_admin = raw_id_admin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BoundField(object):
|
class BoundField(object):
|
||||||
def __init__(self, field, field_mapping, original):
|
def __init__(self, field, field_mapping, original):
|
||||||
self.field = field
|
self.field = field
|
||||||
@ -838,7 +837,7 @@ class BoundFieldLine(object):
|
|||||||
return len(self.bound_fields)
|
return len(self.bound_fields)
|
||||||
|
|
||||||
class FieldLine(object):
|
class FieldLine(object):
|
||||||
def __init__(self, linespec, field_locator_func):
|
def __init__(self, field_locator_func, linespec):
|
||||||
if isinstance(linespec, basestring):
|
if isinstance(linespec, basestring):
|
||||||
self.fields = [field_locator_func(linespec)]
|
self.fields = [field_locator_func(linespec)]
|
||||||
else:
|
else:
|
||||||
@ -871,9 +870,9 @@ class BoundFieldSet(object):
|
|||||||
return len(self.bound_field_lines)
|
return len(self.bound_field_lines)
|
||||||
|
|
||||||
class FieldSet(object):
|
class FieldSet(object):
|
||||||
def __init__(self, name, classes, field_lines):
|
def __init__(self, name, classes, field_locator_func, line_specs):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.field_lines = field_lines
|
self.field_lines = [FieldLine(field_locator_func, line_spec) for line_spec in line_specs]
|
||||||
self.classes = classes
|
self.classes = classes
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -947,7 +946,7 @@ class Admin:
|
|||||||
fs_options = fieldset[1]
|
fs_options = fieldset[1]
|
||||||
classes = fs_options.get('classes', None)
|
classes = fs_options.get('classes', None)
|
||||||
line_specs = fs_options['fields']
|
line_specs = fs_options['fields']
|
||||||
field_lines = [FieldLine(line_spec, opts.get_field) for line_spec in line_specs]
|
new_fieldset_list.append(FieldSet(name, classes, opts.get_field, line_specs) )
|
||||||
new_fieldset_list.append(FieldSet(name, classes, field_lines) )
|
|
||||||
return new_fieldset_list
|
return new_fieldset_list
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ times with multiple contexts)
|
|||||||
'\n<html>\n\n</html>\n'
|
'\n<html>\n\n</html>\n'
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from django.conf.settings import DEFAULT_CHARSET
|
from django.conf.settings import DEFAULT_CHARSET, DEBUG
|
||||||
|
|
||||||
__all__ = ('Template','Context','compile_string')
|
__all__ = ('Template','Context','compile_string')
|
||||||
|
|
||||||
@ -77,7 +77,6 @@ ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01
|
|||||||
#What to report as the origin of templates that come from non file sources (eg strings)
|
#What to report as the origin of templates that come from non file sources (eg strings)
|
||||||
UNKNOWN_SOURCE="<unknown source>"
|
UNKNOWN_SOURCE="<unknown source>"
|
||||||
|
|
||||||
|
|
||||||
#match starts of lines
|
#match starts of lines
|
||||||
newline_re = re.compile("^", re.M);
|
newline_re = re.compile("^", re.M);
|
||||||
|
|
||||||
@ -85,7 +84,6 @@ newline_re = re.compile("^", re.M);
|
|||||||
tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
|
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)))
|
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
|
||||||
|
|
||||||
|
|
||||||
# global dict used by register_tag; maps custom tags to callback functions
|
# global dict used by register_tag; maps custom tags to callback functions
|
||||||
registered_tags = {}
|
registered_tags = {}
|
||||||
|
|
||||||
@ -125,8 +123,15 @@ class Template:
|
|||||||
|
|
||||||
def compile_string(template_string, filename):
|
def compile_string(template_string, filename):
|
||||||
"Compiles template_string into NodeList ready for rendering"
|
"Compiles template_string into NodeList ready for rendering"
|
||||||
tokens = tokenize(template_string, filename)
|
if DEBUG:
|
||||||
parser = Parser(tokens)
|
lexer_factory = DebugLexer
|
||||||
|
parser_factory = DebugParser
|
||||||
|
else:
|
||||||
|
lexer_factory = Lexer
|
||||||
|
parser_factory = Parser
|
||||||
|
|
||||||
|
lexer = lexer_factory(template_string, filename)
|
||||||
|
parser = parser_factory(lexer.tokenize())
|
||||||
return parser.parse()
|
return parser.parse()
|
||||||
|
|
||||||
class Context:
|
class Context:
|
||||||
@ -176,82 +181,100 @@ class Context:
|
|||||||
self.dicts = [other_dict] + self.dicts
|
self.dicts = [other_dict] + self.dicts
|
||||||
|
|
||||||
class Token:
|
class Token:
|
||||||
def __init__(self, token_type, contents, source):
|
def __init__(self, token_type, contents):
|
||||||
"The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
|
"The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
|
||||||
self.token_type, self.contents = token_type, contents
|
self.token_type, self.contents = token_type, contents
|
||||||
self.source = source
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '<%s token: "%s..." from %s, line %d>' % (
|
return '<%s token: "%s...">' % (
|
||||||
{TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type],
|
{TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type],
|
||||||
self.contents[:20].replace('\n', ''),
|
self.contents[:20].replace('\n', '')
|
||||||
self.source[0], self.source[1]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def find_linebreaks(template_string):
|
class Lexer(object):
|
||||||
for match in newline_re.finditer(template_string):
|
def __init__(self, template_string, filename):
|
||||||
yield match.start()
|
self.template_string = template_string
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
def tokenize(template_string, filename):
|
def tokenize(self):
|
||||||
"Return a list of tokens from a given template_string"
|
"Return a list of tokens from a given template_string"
|
||||||
|
bits = filter(None, tag_re.split(self.template_string))
|
||||||
|
return map(self.create_token, bits)
|
||||||
|
|
||||||
token_tups = []
|
def create_token(self,token_string):
|
||||||
upto = 0
|
"Convert the given token string into a new Token object and return it"
|
||||||
line = 1
|
if token_string.startswith(VARIABLE_TAG_START):
|
||||||
#TODO:Py2.4 generator expression
|
token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
|
||||||
linebreaks = find_linebreaks(template_string)
|
elif token_string.startswith(BLOCK_TAG_START):
|
||||||
next_linebreak = linebreaks.next()
|
token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
|
||||||
try:
|
else:
|
||||||
for match in tag_re.finditer(template_string):
|
token = Token(TOKEN_TEXT, token_string)
|
||||||
start, end = match.span()
|
return token
|
||||||
if start > upto:
|
|
||||||
token_tups.append( (template_string[upto:start], line) )
|
class DebugLexer(Lexer):
|
||||||
upto = start
|
def __init__(self, template_string, filename):
|
||||||
|
super(DebugLexer,self).__init__(template_string, filename)
|
||||||
|
|
||||||
|
def find_linebreaks(self, template_string):
|
||||||
|
for match in newline_re.finditer(template_string):
|
||||||
|
yield match.start()
|
||||||
|
|
||||||
|
def tokenize(self):
|
||||||
|
"Return a list of tokens from a given template_string"
|
||||||
|
|
||||||
|
token_tups = []
|
||||||
|
upto = 0
|
||||||
|
line = 1
|
||||||
|
#TODO:Py2.4 generator expression
|
||||||
|
linebreaks = self.find_linebreaks(self.template_string)
|
||||||
|
next_linebreak = linebreaks.next()
|
||||||
|
try:
|
||||||
|
for match in tag_re.finditer(self.template_string):
|
||||||
|
start, end = match.span()
|
||||||
|
if start > upto:
|
||||||
|
token_tups.append( (self.template_string[upto:start], line) )
|
||||||
|
upto = start
|
||||||
|
|
||||||
|
while next_linebreak <= upto:
|
||||||
|
next_linebreak = linebreaks.next()
|
||||||
|
line += 1
|
||||||
|
|
||||||
|
token_tups.append( (self.template_string[start:end], line) )
|
||||||
|
upto = end
|
||||||
|
|
||||||
while next_linebreak <= upto:
|
while next_linebreak <= upto:
|
||||||
next_linebreak = linebreaks.next()
|
next_linebreak = linebreaks.next()
|
||||||
line += 1
|
line += 1
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
|
||||||
token_tups.append( (template_string[start:end], line) )
|
last_bit = self.template_string[upto:]
|
||||||
upto = end
|
if len(last_bit):
|
||||||
|
token_tups.append( (last_bit, line) )
|
||||||
|
|
||||||
while next_linebreak <= upto:
|
return [ self.create_token(tok, (self.filename, line)) for tok, line in token_tups]
|
||||||
next_linebreak = linebreaks.next()
|
|
||||||
line += 1
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
|
|
||||||
last_bit = template_string[upto:]
|
|
||||||
if len(last_bit):
|
|
||||||
token_tups.append( (last_bit, line) )
|
|
||||||
|
|
||||||
return [ create_token(tok, (filename, line)) for tok, line in token_tups]
|
|
||||||
|
|
||||||
def create_token(token_string, source):
|
|
||||||
"Convert the given token string into a new Token object and return it"
|
|
||||||
if token_string.startswith(VARIABLE_TAG_START):
|
|
||||||
return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip(), source)
|
|
||||||
elif token_string.startswith(BLOCK_TAG_START):
|
|
||||||
return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip(), source)
|
|
||||||
else:
|
|
||||||
return Token(TOKEN_TEXT, token_string, source)
|
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
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):
|
def __init__(self, tokens):
|
||||||
self.tokens = tokens
|
self.tokens = tokens
|
||||||
self.command_stack = []
|
|
||||||
|
|
||||||
def parse(self, parse_until=[]):
|
def parse(self, parse_until=[]):
|
||||||
nodelist = NodeList()
|
nodelist = NodeList()
|
||||||
while self.tokens:
|
while self.tokens:
|
||||||
token = self.next_token()
|
token = self.next_token()
|
||||||
if token.token_type == TOKEN_TEXT:
|
if token.token_type == TOKEN_TEXT:
|
||||||
nodelist.append(TextNode(token.contents), token)
|
self.extend_nodelist(nodelist, TextNode(token.contents), token)
|
||||||
elif token.token_type == TOKEN_VAR:
|
elif token.token_type == TOKEN_VAR:
|
||||||
if not token.contents:
|
if not token.contents:
|
||||||
raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1])
|
self.empty_variable(token)
|
||||||
nodelist.append(VariableNode(token.contents), token)
|
self.extend_nodelist(nodelist, VariableNode(token.contents), token)
|
||||||
elif token.token_type == TOKEN_BLOCK:
|
elif token.token_type == TOKEN_BLOCK:
|
||||||
if token.contents in parse_until:
|
if token.contents in parse_until:
|
||||||
# put token back on token list so calling code knows why it terminated
|
# put token back on token list so calling code knows why it terminated
|
||||||
@ -260,21 +283,44 @@ class Parser:
|
|||||||
try:
|
try:
|
||||||
command = token.contents.split()[0]
|
command = token.contents.split()[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise TemplateSyntaxError, "Empty block tag"
|
self.empty_block_tag(token)
|
||||||
|
|
||||||
|
# execute callback function for this tag and append resulting node
|
||||||
|
self.enter_command(command, token);
|
||||||
try:
|
try:
|
||||||
# execute callback function for this tag and append resulting node
|
compile_func = registered_tags[command]
|
||||||
self.command_stack.append( (command, token.source) )
|
|
||||||
nodelist.append(registered_tags[command](self, token), token)
|
|
||||||
self.command_stack.pop()
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise TemplateSyntaxError, "Invalid block tag: '%s' at %s, line %d" % (command, token.source[0], token.source[1])
|
self.invalid_block_tag(token, command)
|
||||||
|
|
||||||
|
self.extend_nodelist(nodelist, compile_func(self, token), token)
|
||||||
|
self.exit_command();
|
||||||
|
|
||||||
if parse_until:
|
if parse_until:
|
||||||
(command, (file,line)) = self.command_stack.pop()
|
self.unclosed_block_tag(token)
|
||||||
msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
|
|
||||||
(command, file, line, ', '.join(parse_until) )
|
|
||||||
raise TemplateSyntaxError, msg
|
|
||||||
return nodelist
|
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 empty_variable(self, token):
|
||||||
|
raise TemplateSyntaxError, "Empty variable tag"
|
||||||
|
|
||||||
|
def empty_block_tag(self, token):
|
||||||
|
raise TemplateSyntaxError, "Empty block tag"
|
||||||
|
|
||||||
|
def invalid_block_tag(self, token, command):
|
||||||
|
raise TemplateSyntaxError, "Invalid block tag: %s" % (command)
|
||||||
|
|
||||||
|
def unclosed_block_tag(self, token):
|
||||||
|
raise TemplateSyntaxError, "Unclosed tags: %s " % ', '.join(parse_until)
|
||||||
|
|
||||||
def next_token(self):
|
def next_token(self):
|
||||||
return self.tokens.pop(0)
|
return self.tokens.pop(0)
|
||||||
|
|
||||||
@ -284,6 +330,40 @@ class Parser:
|
|||||||
def delete_first_token(self):
|
def delete_first_token(self):
|
||||||
del self.tokens[0]
|
del self.tokens[0]
|
||||||
|
|
||||||
|
|
||||||
|
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 format_source(self, source):
|
||||||
|
return "at %s, line %d" % source
|
||||||
|
|
||||||
|
def extend_nodelist(self, nodelist, node, token):
|
||||||
|
node.source = token.source
|
||||||
|
super(DebugParser, self).extend_nodelist(nodelist, node, token)
|
||||||
|
|
||||||
|
def empty_variable(self, token):
|
||||||
|
raise TemplateSyntaxError, "Empty variable tag %s" % self.format_source(token.source)
|
||||||
|
|
||||||
|
def empty_block_tag(self, token):
|
||||||
|
raise TemplateSyntaxError, "Empty block tag %s" % self.format_source(token.source)
|
||||||
|
|
||||||
|
def invalid_block_tag(self, token, command):
|
||||||
|
raise TemplateSyntaxError, "Invalid block tag: '%s' %s" % (command, self.format_source(token.source))
|
||||||
|
|
||||||
|
def unclosed_block_tag(self, token):
|
||||||
|
(command, (file,line)) = self.command_stack.pop()
|
||||||
|
msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
|
||||||
|
(command, file, line, ', '.join(parse_until) )
|
||||||
|
raise TemplateSyntaxError, msg
|
||||||
|
|
||||||
class FilterParser:
|
class FilterParser:
|
||||||
"""Parse a variable token and its optional filters (all as a single string),
|
"""Parse a variable token and its optional filters (all as a single string),
|
||||||
and return a list of tuples of the filter name and arguments.
|
and return a list of tuples of the filter name and arguments.
|
||||||
@ -485,7 +565,6 @@ class Node:
|
|||||||
nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
|
nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
class NodeList(list):
|
class NodeList(list):
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
bits = []
|
bits = []
|
||||||
@ -503,11 +582,6 @@ class NodeList(list):
|
|||||||
nodes.extend(node.get_nodes_by_type(nodetype))
|
nodes.extend(node.get_nodes_by_type(nodetype))
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def append(self, node, token = None):
|
|
||||||
if token:
|
|
||||||
node.source = token.source
|
|
||||||
super(NodeList, self).append(node)
|
|
||||||
|
|
||||||
class TextNode(Node):
|
class TextNode(Node):
|
||||||
def __init__(self, s):
|
def __init__(self, s):
|
||||||
self.s = s
|
self.s = s
|
||||||
|
@ -804,6 +804,7 @@ def change_stage_new(request, app_label, module_name, object_id):
|
|||||||
|
|
||||||
for rel_opts, rel_field in inline_related_objects:
|
for rel_opts, rel_field in inline_related_objects:
|
||||||
if rel_opts.order_with_respect_to and rel_opts.order_with_respect_to.rel and rel_opts.order_with_respect_to.rel.to == opts:
|
if rel_opts.order_with_respect_to and rel_opts.order_with_respect_to.rel and rel_opts.order_with_respect_to.rel.to == opts:
|
||||||
|
orig_list = getattr(manipulator.original_object, 'get_%s_list' % opts.get_rel_object_method_name(rel_opts, rel_field))()
|
||||||
form.order_objects.extend(orig_list)
|
form.order_objects.extend(orig_list)
|
||||||
|
|
||||||
c = Context(request, {
|
c = Context(request, {
|
||||||
|
244
docs/design_philosophies.txt
Normal file
244
docs/design_philosophies.txt
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
===================
|
||||||
|
Design philosophies
|
||||||
|
===================
|
||||||
|
|
||||||
|
This document explains some of the fundamental philosophies Django's developers
|
||||||
|
have used in creating the framework. Its goal is to explain the past and guide
|
||||||
|
the future.
|
||||||
|
|
||||||
|
Overall
|
||||||
|
=======
|
||||||
|
|
||||||
|
Loose coupling
|
||||||
|
--------------
|
||||||
|
|
||||||
|
A fundamental goal of Django's stack is `loose coupling and tight cohesion`_.
|
||||||
|
The various layers of the framework shouldn't "know" about each other unless
|
||||||
|
absolutely necessary.
|
||||||
|
|
||||||
|
For example, the template system knows nothing about Web requests, the database
|
||||||
|
layer knows nothing about data display and the view system doesn't care which
|
||||||
|
template system a programmer uses.
|
||||||
|
|
||||||
|
.. _`loose coupling and tight cohesion`: http://c2.com/cgi/wiki?CouplingAndCohesion
|
||||||
|
|
||||||
|
Less code
|
||||||
|
---------
|
||||||
|
|
||||||
|
Django apps should use as little code as possible; they should lack boilerplate.
|
||||||
|
Django should take full advantage of Python's dynamic capabilities, such as
|
||||||
|
introspection.
|
||||||
|
|
||||||
|
Quick development
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The point of a Web framework in the 21st century is to make the tedious aspects
|
||||||
|
of Web development fast. Django should allow for incredibly quick Web
|
||||||
|
development.
|
||||||
|
|
||||||
|
Don't repeat yourself (DRY)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Every distinct concept and/or piece of data should live in one, and only one,
|
||||||
|
place. Redundancy is bad. Normalization is good.
|
||||||
|
|
||||||
|
The framework, within reason, should deduce as much as possible from as little
|
||||||
|
as possible.
|
||||||
|
|
||||||
|
Explicit is better than implicit
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
This, a `core Python principle`_, means Django shouldn't do too much "magic."
|
||||||
|
Magic shouldn't happen unless there's a really good reason for it.
|
||||||
|
|
||||||
|
.. _`core Python principle`: http://www.python.org/doc/Humor.html#zen
|
||||||
|
|
||||||
|
Consistency
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The framework should be consistent at all levels. Consistency applies to
|
||||||
|
everything from low-level (the Python coding style used) to high-level (the
|
||||||
|
"experience" of using Django).
|
||||||
|
|
||||||
|
Models
|
||||||
|
======
|
||||||
|
|
||||||
|
Explicit is better than implicit
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Fields shouldn't assume certain behaviors based solely on the name of the
|
||||||
|
field. This requires too much knowledge of the system and is prone to errors.
|
||||||
|
Instead, behaviors should be based on keyword arguments and, in some cases, on
|
||||||
|
the type of the field.
|
||||||
|
|
||||||
|
Include all relevant domain logic
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Models should encapsulate every aspect of an "object," following Martin
|
||||||
|
Fowler's `Active Record`_ design pattern.
|
||||||
|
|
||||||
|
This is why model-specific admin options are included in the model itself; data
|
||||||
|
related to a model should be stored *in* the model.
|
||||||
|
|
||||||
|
.. _`Active Record`: http://www.martinfowler.com/eaaCatalog/activeRecord.html
|
||||||
|
|
||||||
|
Database API
|
||||||
|
============
|
||||||
|
|
||||||
|
The core goals of the database API are:
|
||||||
|
|
||||||
|
SQL efficiency
|
||||||
|
--------------
|
||||||
|
|
||||||
|
It should execute SQL statements as few times as possible, and it should
|
||||||
|
optimize statements internally.
|
||||||
|
|
||||||
|
This is why developers need to call ``save()`` explicitly, rather than the
|
||||||
|
framework saving things behind the scenes silently.
|
||||||
|
|
||||||
|
This is also why the ``select_related`` argument exists. It's an optional
|
||||||
|
performance booster for the common case of selecting "every related object."
|
||||||
|
|
||||||
|
Terse, powerful syntax
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The database API should allow rich, expressive statements in as little syntax
|
||||||
|
as possible. It should not rely on importing other modules or helper objects.
|
||||||
|
|
||||||
|
Joins should be performed automatically, behind the scenes, when necessary.
|
||||||
|
|
||||||
|
Every object should be able to access every related object, systemwide. This
|
||||||
|
access should work both ways.
|
||||||
|
|
||||||
|
Option to drop into raw SQL easily, when needed
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
The database API should realize it's a shortcut but not necessarily an
|
||||||
|
end-all-be-all. The framework should make it easy to write custom SQL -- entire
|
||||||
|
statements, or just custom ``WHERE`` clauses as custom parameters to API calls.
|
||||||
|
|
||||||
|
URL design
|
||||||
|
==========
|
||||||
|
|
||||||
|
Loose coupling
|
||||||
|
--------------
|
||||||
|
|
||||||
|
URLs in a Django app should not be coupled to the underlying Python code. Tying
|
||||||
|
URLs to Python function names is a Bad And Ugly Thing.
|
||||||
|
|
||||||
|
Along these lines, the Django URL system should allow URLs for the same app to
|
||||||
|
be different in different contexts. For example, one site may put stories at
|
||||||
|
``/stories/``, while another may use ``/news/``.
|
||||||
|
|
||||||
|
Infinite flexibility
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
URLs should be as flexible as possible. Any conceivable URL design should be
|
||||||
|
allowed.
|
||||||
|
|
||||||
|
Encourage best practices
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The framework should make it just as easy (or even easier) for a developer to
|
||||||
|
design pretty URLs than ugly ones.
|
||||||
|
|
||||||
|
File extensions in Web-page URLs should be avoided.
|
||||||
|
|
||||||
|
Definitive URLs
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Technically, ``foo.com/bar`` and ``foo.com/bar/`` are two different URLs, and
|
||||||
|
search-engine robots (and some Web traffic-analyzing tools) would treat them as
|
||||||
|
separate pages. Django should make an effort to "normalize" URLs so that
|
||||||
|
search-engine robots don't get confused.
|
||||||
|
|
||||||
|
This is the reasoning behind the ``APPEND_SLASH`` setting.
|
||||||
|
|
||||||
|
Template system
|
||||||
|
===============
|
||||||
|
|
||||||
|
Separate logic from presentation
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
We see a template system as a tool that controls presentation and
|
||||||
|
presentation-related logic -- and that's it. The template system shouldn't
|
||||||
|
support functionality that goes beyond this basic goal.
|
||||||
|
|
||||||
|
If we wanted to put everything in templates, we'd be using PHP. Been there,
|
||||||
|
done that, wised up.
|
||||||
|
|
||||||
|
Discourage redundancy
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The majority of dynamic Web sites use some sort of common sitewide design --
|
||||||
|
a common header, footer, navigation bar, etc. The Django template system should
|
||||||
|
make it easy to store those elements in a single place, eliminating duplicate
|
||||||
|
code.
|
||||||
|
|
||||||
|
This is the philosophy behind template inheritance.
|
||||||
|
|
||||||
|
Be decoupled from HTML
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The template system shouldn't be designed so that it only outputs HTML. It
|
||||||
|
should be equally good at generating other text-based formats, or just plain
|
||||||
|
text.
|
||||||
|
|
||||||
|
Assume designer competence
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The template system shouldn't be designed so that templates necessarily are
|
||||||
|
displayed nicely in WYSIWYG editors such as Dreamweaver. That is too severe of
|
||||||
|
a limitation and wouldn't allow the syntax to be as nice as it is. Django
|
||||||
|
expects template authors are comfortable editing HTML directly.
|
||||||
|
|
||||||
|
Treat whitespace obviously
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The template system shouldn't do magic things with whitespace. If a template
|
||||||
|
includes whitespace, the system should treat the whitespace as it treats text
|
||||||
|
-- just display it.
|
||||||
|
|
||||||
|
Don't invent a programming language
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
The template system intentionally doesn't allow the following:
|
||||||
|
|
||||||
|
* Assignment to variables
|
||||||
|
* Advanced logic
|
||||||
|
|
||||||
|
The goal is not to invent a programming language. The goal is to offer just
|
||||||
|
enough programming-esque functionality, such as branching and looping, that is
|
||||||
|
essential for making presentation-related decisions.
|
||||||
|
|
||||||
|
Extensibility
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The template system should recognize that advanced template authors may want
|
||||||
|
to extend its technology.
|
||||||
|
|
||||||
|
This is the philosophy behind custom template tags and filters.
|
||||||
|
|
||||||
|
Views
|
||||||
|
=====
|
||||||
|
|
||||||
|
Simplicity
|
||||||
|
----------
|
||||||
|
|
||||||
|
Writing a view should be as simple as writing a Python function. Developers
|
||||||
|
shouldn't have to instantiate a class when a function will do.
|
||||||
|
|
||||||
|
Use request objects
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Views should have access to a request object -- an object that stores metadata
|
||||||
|
about the current request. The object should be passed directly to a view
|
||||||
|
function, rather than the view function having to access the request data from
|
||||||
|
a global variable. This makes it light, clean and easy to test views by passing
|
||||||
|
in "fake" request objects.
|
||||||
|
|
||||||
|
Loose coupling
|
||||||
|
--------------
|
||||||
|
|
||||||
|
A view shouldn't care about which template system the developer uses -- or even
|
||||||
|
whether a template system is used at all.
|
@ -569,25 +569,28 @@ Built-in filter reference
|
|||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
``add``
|
``add``
|
||||||
Adds the arg to the value
|
Adds the arg to the value.
|
||||||
|
|
||||||
``addslashes``
|
``addslashes``
|
||||||
Adds slashes - useful for passing strings to JavaScript, for example.
|
Adds slashes. Useful for passing strings to JavaScript, for example.
|
||||||
|
|
||||||
``capfirst``
|
``capfirst``
|
||||||
Capitalizes the first character of the value
|
Capitalizes the first character of the value.
|
||||||
|
|
||||||
``center``
|
``center``
|
||||||
Centers the value in a field of a given width
|
Centers the value in a field of a given width.
|
||||||
|
|
||||||
``cut``
|
``cut``
|
||||||
Removes all values of arg from the given string
|
Removes all values of arg from the given string.
|
||||||
|
|
||||||
``date``
|
``date``
|
||||||
Formats a date according to the given format (same as the ``now`` tag)
|
Formats a date according to the given format (same as the ``now`` tag).
|
||||||
|
|
||||||
``default``
|
``default``
|
||||||
If value is unavailable, use given default
|
If value is unavailable, use given default.
|
||||||
|
|
||||||
|
``default_if_none``
|
||||||
|
If value is ``None``, use given default.
|
||||||
|
|
||||||
``dictsort``
|
``dictsort``
|
||||||
Takes a list of dicts, returns that list sorted by the property given in the
|
Takes a list of dicts, returns that list sorted by the property given in the
|
||||||
@ -598,24 +601,24 @@ Built-in filter reference
|
|||||||
given in the argument.
|
given in the argument.
|
||||||
|
|
||||||
``divisibleby``
|
``divisibleby``
|
||||||
Returns true if the value is divisible by the argument
|
Returns true if the value is divisible by the argument.
|
||||||
|
|
||||||
``escape``
|
``escape``
|
||||||
Escapes a string's HTML
|
Escapes a string's HTML.
|
||||||
|
|
||||||
``filesizeformat``
|
``filesizeformat``
|
||||||
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
|
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
|
||||||
bytes, etc).
|
bytes, etc).
|
||||||
|
|
||||||
``first``
|
``first``
|
||||||
Returns the first item in a list
|
Returns the first item in a list.
|
||||||
|
|
||||||
``fix_ampersands``
|
``fix_ampersands``
|
||||||
Replaces ampersands with ``&`` entities
|
Replaces ampersands with ``&`` entities.
|
||||||
|
|
||||||
``floatformat``
|
``floatformat``
|
||||||
Displays a floating point number as 34.2 (with one decimal places) - but
|
Displays a floating point number as 34.2 (with one decimal places) - but
|
||||||
only if there's a point to be displayed
|
only if there's a point to be displayed.
|
||||||
|
|
||||||
``get_digit``
|
``get_digit``
|
||||||
Given a whole number, returns the requested digit of it, where 1 is the
|
Given a whole number, returns the requested digit of it, where 1 is the
|
||||||
@ -624,52 +627,52 @@ Built-in filter reference
|
|||||||
or if argument is less than 1). Otherwise, output is always an integer.
|
or if argument is less than 1). Otherwise, output is always an integer.
|
||||||
|
|
||||||
``join``
|
``join``
|
||||||
Joins a list with a string, like Python's ``str.join(list)``
|
Joins a list with a string, like Python's ``str.join(list)``.
|
||||||
|
|
||||||
``length``
|
``length``
|
||||||
Returns the length of the value - useful for lists
|
Returns the length of the value. Useful for lists.
|
||||||
|
|
||||||
``length_is``
|
``length_is``
|
||||||
Returns a boolean of whether the value's length is the argument
|
Returns a boolean of whether the value's length is the argument.
|
||||||
|
|
||||||
``linebreaks``
|
``linebreaks``
|
||||||
Converts newlines into <p> and <br />s
|
Converts newlines into <p> and <br />s.
|
||||||
|
|
||||||
``linebreaksbr``
|
``linebreaksbr``
|
||||||
Converts newlines into <br />s
|
Converts newlines into <br />s.
|
||||||
|
|
||||||
``linenumbers``
|
``linenumbers``
|
||||||
Displays text with line numbers
|
Displays text with line numbers.
|
||||||
|
|
||||||
``ljust``
|
``ljust``
|
||||||
Left-aligns the value in a field of a given width
|
Left-aligns the value in a field of a given width.
|
||||||
|
|
||||||
**Argument:** field size
|
**Argument:** field size
|
||||||
|
|
||||||
``lower``
|
``lower``
|
||||||
Converts a string into all lowercase
|
Converts a string into all lowercase.
|
||||||
|
|
||||||
``make_list``
|
``make_list``
|
||||||
Returns the value turned into a list. For an integer, it's a list of
|
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.
|
digits. For a string, it's a list of characters.
|
||||||
|
|
||||||
``phone2numeric``
|
``phone2numeric``
|
||||||
Takes a phone number and converts it in to its numerical equivalent
|
Takes a phone number and converts it in to its numerical equivalent.
|
||||||
|
|
||||||
``pluralize``
|
``pluralize``
|
||||||
Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'
|
Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'.
|
||||||
|
|
||||||
``pprint``
|
``pprint``
|
||||||
A wrapper around pprint.pprint -- for debugging, really
|
A wrapper around pprint.pprint -- for debugging, really.
|
||||||
|
|
||||||
``random``
|
``random``
|
||||||
Returns a random item from the list
|
Returns a random item from the list.
|
||||||
|
|
||||||
``removetags``
|
``removetags``
|
||||||
Removes a space separated list of [X]HTML tags from the output
|
Removes a space separated list of [X]HTML tags from the output.
|
||||||
|
|
||||||
``rjust``
|
``rjust``
|
||||||
Right-aligns the value in a field of a given width
|
Right-aligns the value in a field of a given width.
|
||||||
|
|
||||||
**Argument:** field size
|
**Argument:** field size
|
||||||
|
|
||||||
@ -696,19 +699,19 @@ Built-in filter reference
|
|||||||
of Python string formatting
|
of Python string formatting
|
||||||
|
|
||||||
``striptags``
|
``striptags``
|
||||||
Strips all [X]HTML tags
|
Strips all [X]HTML tags.
|
||||||
|
|
||||||
``time``
|
``time``
|
||||||
Formats a time according to the given format (same as the ``now`` tag).
|
Formats a time according to the given format (same as the ``now`` tag).
|
||||||
|
|
||||||
``timesince``
|
``timesince``
|
||||||
Formats a date as the time since that date (i.e. "4 days, 6 hours")
|
Formats a date as the time since that date (i.e. "4 days, 6 hours").
|
||||||
|
|
||||||
``title``
|
``title``
|
||||||
Converts a string into titlecase
|
Converts a string into titlecase.
|
||||||
|
|
||||||
``truncatewords``
|
``truncatewords``
|
||||||
Truncates a string after a certain number of words
|
Truncates a string after a certain number of words.
|
||||||
|
|
||||||
**Argument:** Number of words to truncate after
|
**Argument:** Number of words to truncate after
|
||||||
|
|
||||||
@ -733,26 +736,27 @@ Built-in filter reference
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
``upper``
|
``upper``
|
||||||
Converts a string into all uppercase
|
Converts a string into all uppercase.
|
||||||
|
|
||||||
``urlencode``
|
``urlencode``
|
||||||
Escapes a value for use in a URL
|
Escapes a value for use in a URL.
|
||||||
|
|
||||||
``urlize``
|
``urlize``
|
||||||
Converts URLs in plain text into clickable links
|
Converts URLs in plain text into clickable links.
|
||||||
|
|
||||||
``urlizetrunc``
|
``urlizetrunc``
|
||||||
Converts URLs into clickable links, truncating URLs to the given character limit
|
Converts URLs into clickable links, truncating URLs to the given character
|
||||||
|
limit.
|
||||||
|
|
||||||
**Argument:** Length to truncate URLs to.
|
**Argument:** Length to truncate URLs to
|
||||||
|
|
||||||
``wordcount``
|
``wordcount``
|
||||||
Returns the number of words
|
Returns the number of words.
|
||||||
|
|
||||||
``wordwrap``
|
``wordwrap``
|
||||||
Wraps words at specified line length
|
Wraps words at specified line length.
|
||||||
|
|
||||||
**Argument:** number of words to wrap the text at.
|
**Argument:** number of words at which to wrap the text
|
||||||
|
|
||||||
``yesno``
|
``yesno``
|
||||||
Given a string mapping values for true, false and (optionally) None,
|
Given a string mapping values for true, false and (optionally) None,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user