1
0
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:
Robert Wittams 2005-10-14 15:25:09 +00:00
parent d41ffe4ee4
commit a122c04d47
12 changed files with 489 additions and 165 deletions

View File

@ -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%}

View File

@ -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'

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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, {

View 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.

View File

@ -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 ``&amp;`` entities Replaces ampersands with ``&amp;`` 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,