diff options
author | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2007-06-09 14:08:14 +0000 |
---|---|---|
committer | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2007-06-09 14:08:14 +0000 |
commit | b792044d34171888938dfac833ee4263745c1eef (patch) | |
tree | 91211094ec4697d311ed2de4f93fec79bf790130 | |
parent | 8d30cdc2672175c77319756b42b12103e1efd350 (diff) | |
download | django-b792044d34171888938dfac833ee4263745c1eef.tar.gz |
unicode: Merged from trunk up to [5443].
git-svn-id: http://code.djangoproject.com/svn/django/branches/unicode@5444 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r-- | django/template/defaulttags.py | 56 | ||||
-rw-r--r-- | docs/add_ons.txt | 9 | ||||
-rw-r--r-- | docs/db-api.txt | 13 | ||||
-rw-r--r-- | docs/fastcgi.txt | 2 | ||||
-rw-r--r-- | docs/model-api.txt | 18 | ||||
-rw-r--r-- | docs/modpython.txt | 12 | ||||
-rw-r--r-- | docs/newforms.txt | 62 | ||||
-rw-r--r-- | docs/templates.txt | 31 | ||||
-rw-r--r-- | tests/regressiontests/templates/tests.py | 14 |
9 files changed, 170 insertions, 47 deletions
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 5d2d8a57e0..00c8fab79d 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -6,6 +6,7 @@ from django.template import get_library, Library, InvalidTemplateLibrary from django.conf import settings from django.utils.encoding import smart_str import sys +import re register = Library() @@ -62,8 +63,8 @@ class FirstOfNode(Node): return '' class ForNode(Node): - def __init__(self, loopvar, sequence, reversed, nodelist_loop): - self.loopvar, self.sequence = loopvar, sequence + def __init__(self, loopvars, sequence, reversed, nodelist_loop): + self.loopvars, self.sequence = loopvars, sequence self.reversed = reversed self.nodelist_loop = nodelist_loop @@ -73,7 +74,7 @@ class ForNode(Node): else: reversed = '' return "<For Node: for %s in %s, tail_len: %d%s>" % \ - (self.loopvar, self.sequence, len(self.nodelist_loop), reversed) + (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed) def __iter__(self): for node in self.nodelist_loop: @@ -108,6 +109,7 @@ class ForNode(Node): for index in range(len(data)-1, -1, -1): yield data[index] values = reverse(values) + unpack = len(self.loopvars) > 1 for i, item in enumerate(values): context['forloop'] = { # shortcuts for current loop iteration number @@ -121,9 +123,20 @@ class ForNode(Node): 'last': (i == len_values - 1), 'parentloop': parentloop, } - context[self.loopvar] = item + if unpack: + # If there are multiple loop variables, unpack the item into them. + context.update(dict(zip(self.loopvars, item))) + else: + context[self.loopvars[0]] = item for node in self.nodelist_loop: nodelist.append(node.render(context)) + if unpack: + # The loop variables were pushed on to the context so pop them + # off again. This is necessary because the tag lets the length + # of loopvars differ to the length of each set of items and we + # don't want to leave any vars from the previous loop on the + # context. + context.pop() context.pop() return nodelist.render(context) @@ -487,7 +500,7 @@ def do_filter(parser, token): nodelist = parser.parse(('endfilter',)) parser.delete_first_token() return FilterNode(filter_expr, nodelist) -filter = register.tag("filter", do_filter) +do_filter = register.tag("filter", do_filter) #@register.tag def firstof(parser, token): @@ -531,8 +544,14 @@ def do_for(parser, token): {% endfor %} </ul> - You can also loop over a list in reverse by using + You can loop over a list in reverse by using ``{% for obj in list reversed %}``. + + You can also unpack multiple values from a two-dimensional array:: + + {% for key,value in dict.items %} + {{ key }}: {{ value }} + {% endfor %} The for loop sets a number of variables available within the loop: @@ -553,18 +572,23 @@ def do_for(parser, token): """ bits = token.contents.split() - if len(bits) == 5 and bits[4] != 'reversed': - raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents - if len(bits) not in (4, 5): - raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents - if bits[2] != 'in': - raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents - loopvar = bits[1] - sequence = parser.compile_filter(bits[3]) - reversed = (len(bits) == 5) + if len(bits) < 4: + raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents + + reversed = bits[-1] == 'reversed' + in_index = reversed and -3 or -2 + if bits[in_index] != 'in': + raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents + + loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') + for var in loopvars: + if not var or ' ' in var: + raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents + + sequence = parser.compile_filter(bits[in_index+1]) nodelist_loop = parser.parse(('endfor',)) parser.delete_first_token() - return ForNode(loopvar, sequence, reversed, nodelist_loop) + return ForNode(loopvars, sequence, reversed, nodelist_loop) do_for = register.tag("for", do_for) def do_ifequal(parser, token, negate): diff --git a/docs/add_ons.txt b/docs/add_ons.txt index 4f45d99d9a..ffc4f7420f 100644 --- a/docs/add_ons.txt +++ b/docs/add_ons.txt @@ -6,8 +6,9 @@ Django aims to follow Python's `"batteries included" philosophy`_. It ships with a variety of extra, optional tools that solve common Web-development problems. -This code lives in ``django/contrib`` in the Django distribution. Here's a -rundown of the packages in ``contrib``: +This code lives in ``django/contrib`` in the Django distribution. This document +gives a rundown of the packages in ``contrib``, along with any dependencies +those packages have. .. admonition:: Note @@ -26,6 +27,8 @@ The automatic Django administrative interface. For more information, see .. _Tutorial 2: ../tutorial02/ +Requires the auth_ and contenttypes_ contrib packages to be installed. + auth ==== @@ -144,6 +147,8 @@ See the `flatpages documentation`_. .. _flatpages documentation: ../flatpages/ +Requires the sites_ contrib package to be installed as well. + localflavor =========== diff --git a/docs/db-api.txt b/docs/db-api.txt index 376cc69822..cd3a115282 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -134,6 +134,15 @@ the database until you explicitly call ``save()``. The ``save()`` method has no return value. +Updating ``ForeignKey`` fields works exactly the same way; simply assign an +object of the right type to the field in question:: + + joe = Author.objects.create(name="Joe") + entry.author = joe + entry.save() + +Django will complain if you try to assign an object of the wrong type. + How Django knows to UPDATE vs. INSERT ------------------------------------- @@ -1229,8 +1238,8 @@ whose ``headline`` contains ``'Lennon'``:: Blog.objects.filter(entry__headline__contains='Lennon') -Escaping parenthesis and underscores in LIKE statements -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Escaping percent signs and underscores in LIKE statements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The field lookups that equate to ``LIKE`` SQL statements (``iexact``, ``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith`` diff --git a/docs/fastcgi.txt b/docs/fastcgi.txt index 119688096f..81888bba76 100644 --- a/docs/fastcgi.txt +++ b/docs/fastcgi.txt @@ -203,7 +203,7 @@ This is probably the most common case, if you're using Django's admin site:: DocumentRoot /home/user/public_html Alias /media /home/user/python/django/contrib/admin/media RewriteEngine On - RewriteRule ^/(media.*)$ /$1 [QSA,L] + RewriteRule ^/(media.*)$ /$1 [QSA,L,PT] RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L] </VirtualHost> diff --git a/docs/model-api.txt b/docs/model-api.txt index a25a703d64..0bba2db0a0 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -1890,14 +1890,15 @@ rows. Example:: row = cursor.fetchone() return row -``connection`` and ``cursor`` simply use the standard `Python DB-API`_. If -you're not familiar with the Python DB-API, note that the SQL statement in -``cursor.execute()`` uses placeholders, ``"%s"``, rather than adding parameters -directly within the SQL. If you use this technique, the underlying database -library will automatically add quotes and escaping to your parameter(s) as -necessary. (Also note that Django expects the ``"%s"`` placeholder, *not* the -``"?"`` placeholder, which is used by the SQLite Python bindings. This is for -the sake of consistency and sanity.) +``connection`` and ``cursor`` mostly implement the standard `Python DB-API`_ +(except when it comes to `transaction handling`_). If you're not familiar with +the Python DB-API, note that the SQL statement in ``cursor.execute()`` uses +placeholders, ``"%s"``, rather than adding parameters directly within the SQL. +If you use this technique, the underlying database library will automatically +add quotes and escaping to your parameter(s) as necessary. (Also note that +Django expects the ``"%s"`` placeholder, *not* the ``"?"`` placeholder, which is +used by the SQLite Python bindings. This is for the sake of consistency and +sanity.) A final note: If all you want to do is a custom ``WHERE`` clause, you can just just the ``where``, ``tables`` and ``params`` arguments to the standard lookup @@ -1905,6 +1906,7 @@ API. See `Other lookup options`_. .. _Python DB-API: http://www.python.org/peps/pep-0249.html .. _Other lookup options: ../db-api/#extra-params-select-where-tables +.. _transaction handling: ../transactions/ Overriding default model methods -------------------------------- diff --git a/docs/modpython.txt b/docs/modpython.txt index 43c5791be5..388a6168f3 100644 --- a/docs/modpython.txt +++ b/docs/modpython.txt @@ -51,9 +51,17 @@ whereas ``<Location>`` points at places in the URL structure of a Web site. ``<Directory>`` would be meaningless here. Also, if you've manually altered your ``PYTHONPATH`` to put your Django project -on it, you'll need to tell mod_python:: +on it, you'll need to tell mod_python: - PythonPath "['/path/to/project'] + sys.path" +.. parsed-literal:: + + <Location "/mysite/"> + SetHandler python-program + PythonHandler django.core.handlers.modpython + SetEnv DJANGO_SETTINGS_MODULE mysite.settings + PythonDebug On + **PythonPath "['/path/to/project'] + sys.path"** + </Location> .. caution:: diff --git a/docs/newforms.txt b/docs/newforms.txt index b8b6e00a96..aef7ffa967 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -299,12 +299,14 @@ required. In this example, the data dictionary doesn't include a value for the In this above example, the ``cleaned_data`` value for ``nick_name`` is set to an empty string, because ``nick_name`` is ``CharField``, and ``CharField``\s treat empty values as an empty string. Each field type knows what its "blank" value -is -- e.g., for ``DateField``, it's ``None`` instead of the empty string. +is -- e.g., for ``DateField``, it's ``None`` instead of the empty string. For +full details on each field's behavior in this case, see the "Empty value" note +for each field in the "Built-in ``Field`` classes" section below. Behavior of unbound forms ~~~~~~~~~~~~~~~~~~~~~~~~~ -It's meaningless to request "clean" data in a form with no data, but, for the +It's meaningless to request "cleaned" data in a form with no data, but, for the record, here's what happens with unbound forms:: >>> f = ContactForm() @@ -606,8 +608,13 @@ Using forms in views and templates ---------------------------------- Let's put this all together and use the ``ContactForm`` example in a Django -view and template. This example view displays the contact form by default and -validates/processes it if accessed via a POST request:: +view and template. + +Simple view example +~~~~~~~~~~~~~~~~~~~ + +This example view displays the contact form by default and validates/processes +it if accessed via a POST request:: def contact(request): if request.method == 'POST': @@ -619,12 +626,12 @@ validates/processes it if accessed via a POST request:: form = ContactForm() return render_to_response('contact.html', {'form': form}) -Simple template output -~~~~~~~~~~~~~~~~~~~~~~ +Simple template example +~~~~~~~~~~~~~~~~~~~~~~~ -The template, ``contact.html``, is responsible for displaying the form as HTML. -To do this, we can use the techniques outlined in the "Outputting forms as HTML" -section above. +The template in the above view example, ``contact.html``, is responsible for +displaying the form as HTML. To do this, we can use the techniques outlined in +the "Outputting forms as HTML" section above. The simplest way to display a form's HTML is to use the variable on its own, like this:: @@ -677,7 +684,7 @@ The easiest way is to iterate over the form's fields, with This iteration technique is useful if you want to apply the same HTML formatting to each field, or if you don't know the names of the form fields -ahead of time. Note that the fields will be listed in the order in which +ahead of time. Note that the fields will be iterated over in the order in which they're defined in the ``Form`` class. Alternatively, you can arrange the form's fields explicitly, by name. Do that @@ -701,7 +708,10 @@ For example:: Subclassing forms ----------------- -If you subclass a custom ``Form`` class, the resulting ``Form`` class will +If you have multiple ``Form`` classes that share fields, you can use +subclassing to remove redundancy. + +When you subclass a custom ``Form`` class, the resulting subclass will include all fields of the parent class(es), followed by the fields you define in the subclass. @@ -1202,6 +1212,36 @@ custom ``Field`` classes. To do this, just create a subclass of mentioned above (``required``, ``label``, ``initial``, ``widget``, ``help_text``). +A simple example +~~~~~~~~~~~~~~~~ + +Here's a simple example of a custom field that validates its input is a string +containing comma-separated e-mail addresses, with at least one address. We'll +keep it simple and assume e-mail validation is contained in a function called +``is_valid_email()``. The full class:: + + from django import newforms as forms + + class MultiEmailField(forms.Field): + def clean(self, value): + emails = value.split(',') + for email in emails: + if not is_valid_email(email): + raise forms.ValidationError('%s is not a valid e-mail address.' % email) + if not emails: + raise forms.ValidationError('Enter at least one e-mail address.') + return emails + +Let's alter the ongoing ``ContactForm`` example to demonstrate how you'd use +this in a form. Simply use ``MultiEmailField`` instead of ``forms.EmailField``, +like so:: + + class ContactForm(forms.Form): + subject = forms.CharField(max_length=100) + message = forms.CharField() + senders = MultiEmailField() + cc_myself = forms.BooleanField() + Generating forms for models =========================== diff --git a/docs/templates.txt b/docs/templates.txt index 8d2b429e3f..41289248c1 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -91,9 +91,12 @@ Filters can be "chained." The output of one filter is applied to the next. ``{{ text|escape|linebreaks }}`` is a common idiom for escaping text contents, then converting line breaks to ``<p>`` tags. -Some filters take arguments. A filter argument looks like this: -``{{ bio|truncatewords:"30" }}``. This will display the first 30 words of the -``bio`` variable. Filter arguments always are in double quotes. +Some filters take arguments. A filter argument looks like this: ``{{ +bio|truncatewords:30 }}``. This will display the first 30 words of the ``bio`` +variable. + +Filter arguments that contain spaces must be quoted; for example, to join a list +with commas and spaced you'd use ``{{ list|join:", " }}``. The `Built-in filter reference`_ below describes all the built-in filters. @@ -444,7 +447,7 @@ for ~~~ Loop over each item in an array. For example, to display a list of athletes -given ``athlete_list``:: +provided in ``athlete_list``:: <ul> {% for athlete in athlete_list %} @@ -452,7 +455,25 @@ given ``athlete_list``:: {% endfor %} </ul> -You can also loop over a list in reverse by using ``{% for obj in list reversed %}``. +You can loop over a list in reverse by using ``{% for obj in list reversed %}``. + +**New in Django development version** +If you need to loop over a list of lists, you can unpack the values +in eachs sub-list into a set of known names. For example, if your context contains +a list of (x,y) coordinates called ``points``, you could use the following +to output the list of points:: + + {% for x, y in points %} + There is a point at {{ x }},{{ y }} + {% endfor %} + +This can also be useful if you need to access the items in a dictionary. +For example, if your context contained a dictionary ``data``, the following +would display the keys and values of the dictionary:: + + {% for key, value in data.items %} + {{ key }}: {{ value }} + {% endfor %} The for loop sets a number of variables available within the loop: diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 141e512409..349daaf337 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -295,6 +295,20 @@ class Templates(unittest.TestCase): 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"), 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"), 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"), + 'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack05': ("{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + 'for-tag-unpack06': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), + 'for-tag-unpack07': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), + 'for-tag-unpack08': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), + # Ensure that a single loopvar doesn't truncate the list in val. + 'for-tag-unpack09': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), + # Otherwise, silently truncate if the length of loopvars differs to the length of each set of items. + 'for-tag-unpack10': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"), + 'for-tag-unpack11': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, ("one:1,/two:2,/", "one:1,INVALID/two:2,INVALID/")), + 'for-tag-unpack12': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")), + 'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")), ### IF TAG ################################################################ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"), |