diff --git a/django/conf/admin_templates/admin_change_form.html b/django/conf/admin_templates/admin_change_form.html index 120ebd29bd..b40e04328c 100644 --- a/django/conf/admin_templates/admin_change_form.html +++ b/django/conf/admin_templates/admin_change_form.html @@ -2,9 +2,7 @@ {% load admin_modify %} {% load adminmedia %} {% block extrahead %} - {% for js in javascript_imports %} - {% include_admin_script js %} - {% endfor %} +{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %} {% endblock %} {% block coltype %}{{ coltype }}{% 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 %} {% endif %}{% endblock %} - {% block content %}
-{% if change %} - {% if not is_popup %} - - {% endif %} -{% endif %} - +{% if change %}{% if not is_popup %} + +{% endif %}{% endif %}
- {% if is_popup %}{% endif %} - -{% if save_on_top %} - {% submit_row %} -{% endif %} - -{% if form.error_dict %} -

Please correct the error{{ form.error_dict.items|pluralize }} below.

-{% endif %} +{% if save_on_top %}{% submit_row %}{% endif %} +{% if form.error_dict %}

Please correct the error{{ form.error_dict.items|pluralize }} below.

{% endif %} {% for bound_field_set in bound_field_sets %}
- {% if bound_field_set.name %} -

{{bound_field_set.name }}

- {% endif %} + {% if bound_field_set.name %}

{{bound_field_set.name }}

{% endif %} {% for bound_field_line in bound_field_set %} {% admin_field_line bound_field_line %} {% for bound_field in bound_field_line %} @@ -53,7 +36,6 @@ {% endfor %}
{% endfor %} - {% if change %} {% if ordered_objects %}

Ordering

@@ -63,11 +45,7 @@
{% endif %} {% endif %} - - -{% for relation in inline_related_objects %} - {% edit_inline relation %} -{% endfor %} +{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %} {% submit_row %} @@ -88,7 +66,7 @@
  • {{ object|truncatewords:"5" }}
  • - {% endfor%} + {% endfor%} {% endif %} {% endif %} {% endif%} diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index c651543ff9..ea2fc440de 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -49,7 +49,8 @@ DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', or 'sqlite3'. DATABASE_NAME = '' DATABASE_USER = '' 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. EMAIL_HOST = 'localhost' diff --git a/django/conf/project_template/settings/main.py b/django/conf/project_template/settings/main.py index 56b1f023ce..38df2ad01d 100644 --- a/django/conf/project_template/settings/main.py +++ b/django/conf/project_template/settings/main.py @@ -15,6 +15,7 @@ DATABASE_NAME = '' # Or path to database file if using sqlite3. DATABASE_USER = '' # Not used with sqlite3. DATABASE_PASSWORD = '' # 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 diff --git a/django/core/db/backends/mysql.py b/django/core/db/backends/mysql.py index af0dbca6c0..4399b6a6a0 100644 --- a/django/core/db/backends/mysql.py +++ b/django/core/db/backends/mysql.py @@ -53,10 +53,18 @@ class DatabaseWrapper: self.queries = [] 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: - self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME, - passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions) + kwargs = { + '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: return base.CursorDebugWrapper(MysqlDebugWrapper(self.connection.cursor()), self) return self.connection.cursor() diff --git a/django/core/db/backends/postgresql.py b/django/core/db/backends/postgresql.py index 6ec7bfbfcb..c922fd42f6 100644 --- a/django/core/db/backends/postgresql.py +++ b/django/core/db/backends/postgresql.py @@ -15,7 +15,7 @@ class DatabaseWrapper: self.queries = [] 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 DATABASE_NAME == '': from django.core.exceptions import ImproperlyConfigured @@ -27,6 +27,8 @@ class DatabaseWrapper: conn_string += " password=%s" % DATABASE_PASSWORD if 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.set_isolation_level(1) # make transactions transparent to all cursors cursor = self.connection.cursor() diff --git a/django/core/defaultfilters.py b/django/core/defaultfilters.py index eb3ec11c95..42b46d03ea 100644 --- a/django/core/defaultfilters.py +++ b/django/core/defaultfilters.py @@ -333,6 +333,12 @@ 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 diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py index 7f32913fbc..aec45dcd88 100644 --- a/django/core/meta/__init__.py +++ b/django/core/meta/__init__.py @@ -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 ] def get_follow(self, override=None): - if override: - over = override.copy() - elif self.edit_inline: - over = {} + if isinstance(override, bool): + if override: + over = {} + else: + return None else: - return None + if override: + over = override.copy() + elif self.edit_inline: + over = {} + else: + return None over[self.field.name] = False return self.opts.get_follow(over) @@ -1754,7 +1760,7 @@ def manipulator_save(opts, klass, add, change, self, new_data): return new_object def manipulator_get_inline_related_objects_wrapped(opts, klass, add, change, self): - return opts.get_inline_related_objects_wrapped() + return opts.get_inline_related_objects_wrapped() def manipulator_flatten_data(opts, klass, add, change, self): new_data = {} diff --git a/django/core/meta/fields.py b/django/core/meta/fields.py index 9fa250a96d..05d00a13c7 100644 --- a/django/core/meta/fields.py +++ b/django/core/meta/fields.py @@ -804,7 +804,6 @@ class OneToOne(ManyToOne): self.raw_id_admin = raw_id_admin - class BoundField(object): def __init__(self, field, field_mapping, original): self.field = field @@ -838,7 +837,7 @@ class BoundFieldLine(object): return len(self.bound_fields) class FieldLine(object): - def __init__(self, linespec, field_locator_func): + def __init__(self, field_locator_func, linespec): if isinstance(linespec, basestring): self.fields = [field_locator_func(linespec)] else: @@ -871,9 +870,9 @@ class BoundFieldSet(object): return len(self.bound_field_lines) class FieldSet(object): - def __init__(self, name, classes, field_lines): + def __init__(self, name, classes, field_locator_func, line_specs): 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 def __repr__(self): @@ -947,7 +946,7 @@ class Admin: fs_options = fieldset[1] classes = fs_options.get('classes', None) 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, field_lines) ) + new_fieldset_list.append(FieldSet(name, classes, opts.get_field, line_specs) ) return new_fieldset_list - \ No newline at end of file + + \ No newline at end of file diff --git a/django/core/template.py b/django/core/template.py index 3ba2865763..0876af76f0 100644 --- a/django/core/template.py +++ b/django/core/template.py @@ -55,7 +55,7 @@ times with multiple contexts) '\n\n\n\n' """ import re -from django.conf.settings import DEFAULT_CHARSET +from django.conf.settings import DEFAULT_CHARSET, DEBUG __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) UNKNOWN_SOURCE="" - #match starts of lines 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), re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) - # global dict used by register_tag; maps custom tags to callback functions registered_tags = {} @@ -125,8 +123,15 @@ class Template: def compile_string(template_string, filename): "Compiles template_string into NodeList ready for rendering" - tokens = tokenize(template_string, filename) - parser = Parser(tokens) + if DEBUG: + 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() class Context: @@ -176,82 +181,100 @@ class Context: self.dicts = [other_dict] + self.dicts 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" self.token_type, self.contents = token_type, contents - self.source = source 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], - self.contents[:20].replace('\n', ''), - self.source[0], self.source[1] + self.contents[:20].replace('\n', '') ) -def find_linebreaks(template_string): - for match in newline_re.finditer(template_string): - yield match.start() - -def tokenize(template_string, filename): - "Return a list of tokens from a given template_string" +class Lexer(object): + def __init__(self, template_string, filename): + self.template_string = template_string + self.filename = filename - token_tups = [] - upto = 0 - line = 1 - #TODO:Py2.4 generator expression - linebreaks = find_linebreaks(template_string) - next_linebreak = linebreaks.next() - try: - for match in tag_re.finditer(template_string): - start, end = match.span() - if start > upto: - token_tups.append( (template_string[upto:start], line) ) - upto = start + def tokenize(self): + "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) + + 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, 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: next_linebreak = linebreaks.next() line += 1 - - token_tups.append( (template_string[start:end], line) ) - upto = end - - while next_linebreak <= upto: - 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) + except StopIteration: + pass + + last_bit = self.template_string[upto:] + if len(last_bit): + token_tups.append( (last_bit, line) ) + + return [ self.create_token(tok, (self.filename, line)) for tok, line in token_tups] -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): self.tokens = tokens - self.command_stack = [] def parse(self, parse_until=[]): nodelist = NodeList() while self.tokens: token = self.next_token() 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: if not token.contents: - raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1]) - nodelist.append(VariableNode(token.contents), token) + self.empty_variable(token) + self.extend_nodelist(nodelist, VariableNode(token.contents), 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 @@ -260,21 +283,44 @@ class Parser: try: command = token.contents.split()[0] 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: - # execute callback function for this tag and append resulting node - self.command_stack.append( (command, token.source) ) - nodelist.append(registered_tags[command](self, token), token) - self.command_stack.pop() + compile_func = registered_tags[command] 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: - (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 + self.unclosed_block_tag(token) + 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): return self.tokens.pop(0) @@ -284,6 +330,40 @@ class Parser: def delete_first_token(self): 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: """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. @@ -484,7 +564,6 @@ class Node: if hasattr(self, 'nodelist'): nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) return nodes - class NodeList(list): def render(self, context): @@ -502,11 +581,6 @@ class NodeList(list): for node in self: nodes.extend(node.get_nodes_by_type(nodetype)) return nodes - - def append(self, node, token = None): - if token: - node.source = token.source - super(NodeList, self).append(node) class TextNode(Node): def __init__(self, s): diff --git a/django/views/admin/main.py b/django/views/admin/main.py index fcce2533cc..7c2a66fd25 100644 --- a/django/views/admin/main.py +++ b/django/views/admin/main.py @@ -634,7 +634,7 @@ def fill_extra_context(opts, app_label, context, add=False, change=False, show_d ordered_object_names = ' '.join(['object.%s' % o.pk.name for o in ordered_objects]) extra_context = { - 'add': add, + 'add': add, 'change': change, 'first_form_field_id': first_form_field.get_id(), 'ordered_objects' : ordered_objects, @@ -804,6 +804,7 @@ def change_stage_new(request, app_label, module_name, object_id): 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: + 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) c = Context(request, { diff --git a/docs/design_philosophies.txt b/docs/design_philosophies.txt new file mode 100644 index 0000000000..2084c992a5 --- /dev/null +++ b/docs/design_philosophies.txt @@ -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. diff --git a/docs/templates.txt b/docs/templates.txt index a6848a9638..843ed0cbaa 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -376,9 +376,9 @@ Built-in tag reference ========================== ================================================ ``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 + ``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 + ``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 @@ -569,25 +569,28 @@ Built-in filter reference ------------------------- ``add`` - Adds the arg to the value + Adds the arg to the value. ``addslashes`` - Adds slashes - useful for passing strings to JavaScript, for example. + Adds slashes. Useful for passing strings to JavaScript, for example. ``capfirst`` - Capitalizes the first character of the value + Capitalizes the first character of the value. ``center`` - Centers the value in a field of a given width + Centers the value in a field of a given width. ``cut`` - Removes all values of arg from the given string + Removes all values of arg from the given string. ``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`` - 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`` 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. ``divisibleby`` - Returns true if the value is divisible by the argument + Returns true if the value is divisible by the argument. ``escape`` - Escapes a string's HTML + Escapes a string's HTML. ``filesizeformat`` Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 bytes, etc). ``first`` - Returns the first item in a list + Returns the first item in a list. ``fix_ampersands`` - Replaces ampersands with ``&`` entities + Replaces ampersands with ``&`` entities. ``floatformat`` 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`` 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. ``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`` - Returns the length of the value - useful for lists + Returns the length of the value. Useful for lists. ``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`` - Converts newlines into

    and
    s + Converts newlines into

    and
    s. ``linebreaksbr`` - Converts newlines into
    s + Converts newlines into
    s. ``linenumbers`` - Displays text with line numbers + Displays text with line numbers. ``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 ``lower`` - Converts a string into all lowercase + Converts a string into all lowercase. ``make_list`` 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. ``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`` - 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`` - A wrapper around pprint.pprint -- for debugging, really + A wrapper around pprint.pprint -- for debugging, really. ``random`` - Returns a random item from the list + Returns a random item from the list. ``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`` - 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 @@ -696,19 +699,19 @@ Built-in filter reference of Python string formatting ``striptags`` - Strips all [X]HTML tags + Strips all [X]HTML tags. ``time`` Formats a time according to the given format (same as the ``now`` tag). ``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`` - Converts a string into titlecase + Converts a string into titlecase. ``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 @@ -733,26 +736,27 @@ Built-in filter reference ``upper`` - Converts a string into all uppercase + Converts a string into all uppercase. ``urlencode`` - Escapes a value for use in a URL + Escapes a value for use in a URL. ``urlize`` - Converts URLs in plain text into clickable links + Converts URLs in plain text into clickable links. ``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`` - Returns the number of words + Returns the number of words. ``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`` Given a string mapping values for true, false and (optionally) None,