Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

queryset-refactor: Merged to [6155]

git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6332 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit fb6a0c8ffa1cd74c63aaf4b011665e5952d449e7 1 parent c8f6e48
Adrian Holovaty adrianholovaty authored
Showing with 717 additions and 100 deletions.
  1. +2 −0  AUTHORS
  2. +5 −1 django/contrib/admin/media/js/urlify.js
  3. +1 −1  django/contrib/admin/templates/admin/change_list_results.html
  4. +1 −1  django/contrib/databrowse/templates/databrowse/calendar_day.html
  5. +1 −1  django/contrib/databrowse/templates/databrowse/calendar_homepage.html
  6. +1 −1  django/contrib/databrowse/templates/databrowse/calendar_main.html
  7. +1 −1  django/contrib/databrowse/templates/databrowse/calendar_month.html
  8. +1 −1  django/contrib/databrowse/templates/databrowse/calendar_year.html
  9. +1 −1  django/contrib/databrowse/templates/databrowse/choice_detail.html
  10. +1 −1  django/contrib/databrowse/templates/databrowse/choice_list.html
  11. +1 −1  django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html
  12. +1 −1  django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html
  13. +1 −1  django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
  14. +1 −1  django/contrib/databrowse/templates/databrowse/homepage.html
  15. +1 −1  django/contrib/databrowse/templates/databrowse/model_detail.html
  16. +2 −2 django/contrib/databrowse/templates/databrowse/object_detail.html
  17. 0  django/contrib/localflavor/ar/__init__.py
  18. +36 −0 django/contrib/localflavor/ar/ar_provinces.py
  19. +105 −0 django/contrib/localflavor/ar/forms.py
  20. +11 −0 django/core/paginator.py
  21. +1 −0  django/db/models/fields/__init__.py
  22. +8 −6 django/newforms/fields.py
  23. +6 −4 django/newforms/forms.py
  24. +22 −26 django/template/defaulttags.py
  25. +21 −11 django/views/generic/list_detail.py
  26. +21 −6 docs/generic_views.txt
  27. +34 −0 docs/newforms.txt
  28. +3 −0  docs/request_response.txt
  29. +3 −0  docs/sites.txt
  30. +18 −7 docs/templates.txt
  31. +19 −13 docs/templates_python.txt
  32. +5 −3 docs/testing.txt
  33. +4 −0 tests/modeltests/pagination/models.py
  34. +290 −0 tests/regressiontests/forms/localflavor.py
  35. +35 −8 tests/regressiontests/forms/tests.py
  36. +45 −0 tests/regressiontests/forms/util.py
  37. +8 −0 tests/regressiontests/templates/tests.py
2  AUTHORS
View
@@ -127,6 +127,7 @@ answer newbie questions, and generally made Django that much better:
Dimitris Glezos <dimitris@glezos.com>
glin@seznam.cz
martin.glueck@gmail.com
+ Artyom Gnilov <boobsd@gmail.com>
GomoX <gomo@datafull.com>
Mario Gonzalez <gonzalemario@gmail.com>
pradeep.gowda@gmail.com
@@ -240,6 +241,7 @@ answer newbie questions, and generally made Django that much better:
Jan Rademaker
Michael Radziej <mir@noris.de>
Amit Ramon <amit.ramon@gmail.com>
+ Philippe Raoult <philippe.raoult@n2nsoft.com>
Massimiliano Ravelli <massimiliano.ravelli@gmail.com>
Brian Ray <http://brianray.chipy.org/>
remco@diji.biz
6 django/contrib/admin/media/js/urlify.js
View
@@ -40,6 +40,9 @@ var RUSSIAN_MAP = {
'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu',
'Я':'Ya'
}
+var UKRAINIAN_MAP = {
+ 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g'
+}
var CZECH_MAP = {
'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u',
'ž':'z'
@@ -51,7 +54,8 @@ ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP
ALL_DOWNCODE_MAPS[2]=GREEK_MAP
ALL_DOWNCODE_MAPS[3]=TURKISH_MAP
ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP
-ALL_DOWNCODE_MAPS[5]=CZECH_MAP
+ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP
+ALL_DOWNCODE_MAPS[6]=CZECH_MAP
var Downcoder = new Object();
Downcoder.Initialize = function()
2  django/contrib/admin/templates/admin/change_list_results.html
View
@@ -10,7 +10,7 @@
</thead>
<tbody>
{% for result in results %}
-<tr class="{% cycle row1,row2 %}">{% for item in result %}{{ item }}{% endfor %}</tr>
+<tr class="{% cycle 'row1' 'row2' %}">{% for item in result %}{{ item }}{% endfor %}</tr>
{% endfor %}
</tbody>
</table>
2  django/contrib/databrowse/templates/databrowse/calendar_day.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for object in object_list %}
-<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/calendar_homepage.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for field in field_list %}
-<li class="{% cycle odd,even %}"><a href="{{ field.name }}/">{{ model.verbose_name_plural|capfirst }} by {{ field.verbose_name }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ field.name }}/">{{ model.verbose_name_plural|capfirst }} by {{ field.verbose_name }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/calendar_main.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for year in date_list %}
-<li class="{% cycle odd,even %}"><a href="{{ year.year }}/">{{ year.year }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ year.year }}/">{{ year.year }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/calendar_month.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for object in object_list %}
-<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/calendar_year.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for month in date_list %}
-<li class="{% cycle odd,even %}"><a href="{{ month|date:"M"|lower }}/">{{ month|date:"F" }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ month|date:"M"|lower }}/">{{ month|date:"F" }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/choice_detail.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for object in object_list %}
-<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/choice_list.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for choice in field.choices %}
-<li class="{% cycle odd,even %}"><a href="{{ choice.url }}">{{ choice.label|escape }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ choice.url }}">{{ choice.label|escape }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for object in object_list %}
-<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for field in field_list %}
-<li class="{% cycle odd,even %}"><a href="{{ field.name }}/">{{ model.verbose_name_plural|capfirst }} by {{ field.verbose_name }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ field.name }}/">{{ model.verbose_name_plural|capfirst }} by {{ field.verbose_name }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
View
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for object in object_list %}
-<li class="{% cycle odd,even %}"><a href="{{ object|iriencode }}/">{{ object|escape }}</a></li>
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object|iriencode }}/">{{ object|escape }}</a></li>
{% endfor %}
</ul>
2  django/contrib/databrowse/templates/databrowse/homepage.html
View
@@ -7,7 +7,7 @@
{% block content %}
{% for model in model_list %}
- <div class="modelgroup {% cycle even,odd %}">
+ <div class="modelgroup {% cycle 'even' 'odd' %}">
<h2><a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a></h2>
<p>
{% for object in model.sample_objects %}
2  django/contrib/databrowse/templates/databrowse/model_detail.html
View
@@ -12,7 +12,7 @@
<ul class="objectlist">
{% for object in model.objects %}
- <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+ <li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
{% endfor %}
</ul>
4 django/contrib/databrowse/templates/databrowse/object_detail.html
View
@@ -10,7 +10,7 @@
<table class="objectinfo">
{% for field in object.fields %}
-<tr class="{% cycle odd,even %}">
+<tr class="{% cycle 'odd' 'even' %}">
<th>{{ field.field.verbose_name|capfirst }}</th>
<td>
{% if field.urls %}
@@ -29,7 +29,7 @@
{% if related_object.object_list %}
<ul class="objectlist">
{% for object in related_object.object_list %}
- <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+ <li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
{% endfor %}
</ul>
{% else %}
0  django/contrib/localflavor/ar/__init__.py
View
No changes.
36 django/contrib/localflavor/ar/ar_provinces.py
View
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+"""
+A list of Argentinean provinces and autonomous cities as `choices` in a
+formfield. From
+http://www.argentina.gov.ar/argentina/portal/paginas.dhtml?pagina=425
+
+This exists in this standalone file so that it's only imported into memory
+when explicitly needed.
+"""
+
+PROVINCE_CHOICES = (
+ ('B', u'Buenos Aires'),
+ ('K', u'Catamarca'),
+ ('H', u'Chaco'),
+ ('U', u'Chubut'),
+ ('C', u'Ciudad Autónoma de Buenos Aires'),
+ ('X', u'Córdoba'),
+ ('W', u'Corrientes'),
+ ('E', u'Entre Ríos'),
+ ('P', u'Formosa'),
+ ('Y', u'Jujuy'),
+ ('L', u'La Pampa'),
+ ('F', u'La Rioja'),
+ ('M', u'Mendoza'),
+ ('N', u'Misiones'),
+ ('Q', u'Neuquén'),
+ ('R', u'Río Negro'),
+ ('A', u'Salta'),
+ ('J', u'San Juan'),
+ ('D', u'San Luis'),
+ ('Z', u'Santa Cruz'),
+ ('S', u'Santa Fe'),
+ ('G', u'Santiago del Estero'),
+ ('V', u'Tierra del Fuego, Antártida e Islas del Atlántico Sur'),
+ ('T', u'Tucumán'),
+)
105 django/contrib/localflavor/ar/forms.py
View
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+"""
+AR-specific Form helpers.
+"""
+
+from django.newforms import ValidationError
+from django.newforms.fields import RegexField, CharField, Select, EMPTY_VALUES
+from django.utils.encoding import smart_unicode
+from django.utils.translation import ugettext
+import re
+
+class ARProvinceSelect(Select):
+ """
+ A Select widget that uses a list of Argentinean provinces/autonomous cities
+ as its choices.
+ """
+ def __init__(self, attrs=None):
+ from ar_provinces import PROVINCE_CHOICES
+ super(ARProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
+
+class ARPostalCodeField(RegexField):
+ """
+ A field that accepts a `classic´ NNNN Postal Code or a CPA.
+
+ See http://www.correoargentino.com.ar/consulta_cpa/home.php
+ """
+ def __init__(self, *args, **kwargs):
+ super(ARPostalCodeField, self).__init__(r'^\d{4}$|^[A-HJ-NP-Za-hj-np-z]\d{4}\D{3}$',
+ min_length=4, max_length=8,
+ error_message=ugettext("Enter a postal code in the format NNNN or ANNNNAAA."),
+ *args, **kwargs)
+
+ def clean(self, value):
+ value = super(ARPostalCodeField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ if len(value) not in (4, 8):
+ raise ValidationError(ugettext("Enter a postal code in the format NNNN or ANNNNAAA."))
+ if len(value) == 8:
+ return u'%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper())
+ return value
+
+class ARDNIField(CharField):
+ """
+ A field that validates `Documento Nacional de Identidad´ (DNI) numbers.
+ """
+ def __init__(self, *args, **kwargs):
+ super(ARDNIField, self).__init__(max_length=10, min_length=7, *args,
+ **kwargs)
+
+ def clean(self, value):
+ """
+ Value can be a string either in the [X]X.XXX.XXX or [X]XXXXXXX formats.
+ """
+ value = super(ARDNIField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ if not value.isdigit():
+ value = value.replace('.', '')
+ if not value.isdigit():
+ raise ValidationError(ugettext("This field requires only numbers."))
+ if len(value) not in (7, 8):
+ raise ValidationError(
+ ugettext("This field requires 7 or 8 digits."))
+
+ return value
+
+class ARCUITField(RegexField):
+ """
+ This field validates a CUIT (Código Único de Identificación Tributaria). A
+ CUIT is of the form XX-XXXXXXXX-V. The last digit is a check digit.
+ """
+ def __init__(self, *args, **kwargs):
+ super(ARCUITField, self).__init__(r'^\d{2}-?\d{8}-?\d$',
+ error_message=ugettext('Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'),
+ *args, **kwargs)
+
+ def clean(self, value):
+ """
+ Value can be either a string in the format XX-XXXXXXXX-X or an
+ 11-digit number.
+ """
+ value = super(ARCUITField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ value, cd = self._canon(value)
+ if self._calc_cd(value) != cd:
+ raise ValidationError(ugettext("Invalid CUIT."))
+ return self._format(value, cd)
+
+ def _canon(self, cuit):
+ cuit = cuit.replace('-', '')
+ return cuit[:-1], cuit[-1]
+
+ def _calc_cd(self, cuit):
+ mults = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2)
+ tmp = sum([m * int(cuit[idx]) for idx, m in enumerate(mults)])
+ return str(11 - tmp % 11)
+
+ def _format(self, cuit, check_digit=None):
+ if check_digit == None:
+ check_digit = cuit[-1]
+ cuit = cuit[:-1]
+ return u'%s-%s-%s' % (cuit[:2], cuit[2:], check_digit)
+
11 django/core/paginator.py
View
@@ -20,6 +20,7 @@ def __init__(self, query_set, num_per_page, orphans=0):
self.num_per_page = num_per_page
self.orphans = orphans
self._hits = self._pages = None
+ self._page_range = None
def validate_page_number(self, page_number):
try:
@@ -83,6 +84,16 @@ def _get_pages(self):
hits = 0
self._pages = hits // self.num_per_page + 1
return self._pages
+
+ def _get_page_range(self):
+ """
+ Returns a 1-based range of pages for iterating through within
+ a template for loop.
+ """
+ if self._page_range is None:
+ self._page_range = range(1, self._pages + 1)
+ return self._page_range
hits = property(_get_hits)
pages = property(_get_pages)
+ page_range = property(_get_page_range)
1  django/db/models/fields/__init__.py
View
@@ -855,6 +855,7 @@ def save_file(self, new_data, new_object, original_object, change, rel, save=Tru
def formfield(self, **kwargs):
defaults = {'form_class': forms.ImageField}
+ defaults.update(kwargs)
return super(ImageField, self).formfield(**defaults)
class IntegerField(Field):
14 django/newforms/fields.py
View
@@ -335,12 +335,6 @@ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
RegexField.__init__(self, email_re, max_length, min_length,
ugettext(u'Enter a valid e-mail address.'), *args, **kwargs)
-url_re = re.compile(
- r'^https?://' # http:// or https://
- r'(?:[A-Z0-9-]+\.)+[A-Z]{2,6}' # domain
- r'(?::\d+)?' # optional port
- r'(?:/?|/\S+)$', re.IGNORECASE)
-
try:
from django.conf import settings
URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
@@ -399,6 +393,14 @@ def clean(self, data):
raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."))
return f
+url_re = re.compile(
+ r'^https?://' # http:// or https://
+ r'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain...
+ r'localhost|' #localhost...
+ r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
+ r'(?::\d+)?' # optional port
+ r'(?:/?|/\S+)$', re.IGNORECASE)
+
class URLField(RegexField):
def __init__(self, max_length=None, min_length=None, verify_exists=False,
validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
10 django/newforms/forms.py
View
@@ -57,13 +57,15 @@ class BaseForm(StrAndUnicode):
# class is different than Form. See the comments by the Form class for more
# information. Any improvements to the form API should be made to *this*
# class, not to the Form class.
- def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ initial=None, error_class=ErrorList):
self.is_bound = data is not None or files is not None
self.data = data or {}
self.files = files or {}
self.auto_id = auto_id
self.prefix = prefix
self.initial = initial or {}
+ self.error_class = error_class
self._errors = None # Stores the errors after clean() has been called.
# The base_fields class attribute is the *class-wide* definition of
@@ -117,7 +119,7 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
output, hidden_fields = [], []
for name, field in self.fields.items():
bf = BoundField(self, field, name)
- bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable.
+ bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
if bf.is_hidden:
if bf_errors:
top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
@@ -168,7 +170,7 @@ def non_field_errors(self):
field -- i.e., from Form.clean(). Returns an empty ErrorList if there
are none.
"""
- return self.errors.get(NON_FIELD_ERRORS, ErrorList())
+ return self.errors.get(NON_FIELD_ERRORS, self.error_class())
def full_clean(self):
"""
@@ -241,7 +243,7 @@ def _errors(self):
Returns an ErrorList for this field. Returns an empty ErrorList
if there are none.
"""
- return self.form.errors.get(self.name, ErrorList())
+ return self.form.errors.get(self.name, self.form.error_class())
errors = property(_errors)
def as_widget(self, widget=None, attrs=None):
48 django/template/defaulttags.py
View
@@ -30,6 +30,7 @@ def __init__(self, cyclevars, variable_name=None):
def render(self, context):
self.counter += 1
value = self.cyclevars[self.counter % self.cyclevars_len]
+ value = resolve_variable(value, context)
if self.variable_name:
context[self.variable_name] = value
return value
@@ -403,7 +404,7 @@ def cycle(parser, token):
the loop::
{% for o in some_list %}
- <tr class="{% cycle row1,row2 %}">
+ <tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
@@ -411,16 +412,17 @@ def cycle(parser, token):
Outside of a loop, give the values a unique name the first time you call
it, then use that name each sucessive time through::
- <tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
+ <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
- You can use any number of values, seperated by commas. Make sure not to
- put spaces between the values -- only commas.
+ You can use any number of values, seperated by spaces. Commas can also
+ be used to separate values; if a comma is used, the cycle values are
+ interpreted as literal strings.
"""
# Note: This returns the exact same node on each {% cycle name %} call; that
- # is, the node object returned from {% cycle a,b,c as name %} and the one
+ # is, the node object returned from {% cycle a b c as name %} and the one
# returned from {% cycle name %} are the exact same object. This shouldn't
# cause problems (heh), but if it does, now you know.
#
@@ -429,40 +431,34 @@ def cycle(parser, token):
# a global variable, which would make cycle names have to be unique across
# *all* templates.
- args = token.contents.split()
+ args = token.split_contents()
+
if len(args) < 2:
- raise TemplateSyntaxError("'Cycle' statement requires at least two arguments")
+ raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
- elif len(args) == 2 and "," in args[1]:
- # {% cycle a,b,c %}
- cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
- return CycleNode(cyclevars)
- # {% cycle name %}
+ if ',' in args[1]:
+ # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
+ # case.
+ args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
- elif len(args) == 2:
+ if len(args) == 2:
+ # {% cycle foo %} case
name = args[1]
if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name)
- if name not in parser._namedCycleNodes:
+ if not name in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
- elif len(args) == 4:
- # {% cycle a,b,c as name %}
- if args[2] != 'as':
- raise TemplateSyntaxError("Second 'cycle' argument must be 'as'")
- cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
- name = args[3]
- node = CycleNode(cyclevars, name)
-
+ if len(args) > 4 and args[-2] == 'as':
+ name = args[-1]
+ node = CycleNode(args[1:-2], name)
if not hasattr(parser, '_namedCycleNodes'):
parser._namedCycleNodes = {}
-
parser._namedCycleNodes[name] = node
- return node
-
else:
- raise TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
+ node = CycleNode(args[1:])
+ return node
cycle = register.tag(cycle)
def debug(parser, token):
32 django/views/generic/list_detail.py
View
@@ -39,6 +39,8 @@ def object_list(request, queryset, paginate_by=None, page=None,
first_on_page
the result number of the first object in the
object_list (1-indexed)
+ page_range:
+ A list of the page numbers (1-indexed).
"""
if extra_context is None: extra_context = {}
queryset = queryset._clone()
@@ -47,10 +49,17 @@ def object_list(request, queryset, paginate_by=None, page=None,
if not page:
page = request.GET.get('page', 1)
try:
- page = int(page)
- object_list = paginator.get_page(page - 1)
- except (InvalidPage, ValueError):
- if page == 1 and allow_empty:
+ page_number = int(page)
+ except ValueError:
+ if page == 'last':
+ page_number = paginator.pages
+ else:
+ # Page is not 'last', nor can it be converted to an int
+ raise Http404
+ try:
+ object_list = paginator.get_page(page_number - 1)
+ except InvalidPage:
+ if page_number == 1 and allow_empty:
object_list = []
else:
raise Http404
@@ -58,15 +67,16 @@ def object_list(request, queryset, paginate_by=None, page=None,
'%s_list' % template_object_name: object_list,
'is_paginated': paginator.pages > 1,
'results_per_page': paginate_by,
- 'has_next': paginator.has_next_page(page - 1),
- 'has_previous': paginator.has_previous_page(page - 1),
- 'page': page,
- 'next': page + 1,
- 'previous': page - 1,
- 'last_on_page': paginator.last_on_page(page - 1),
- 'first_on_page': paginator.first_on_page(page - 1),
+ 'has_next': paginator.has_next_page(page_number - 1),
+ 'has_previous': paginator.has_previous_page(page_number - 1),
+ 'page': page_number,
+ 'next': page_number + 1,
+ 'previous': page_number - 1,
+ 'last_on_page': paginator.last_on_page(page_number - 1),
+ 'first_on_page': paginator.first_on_page(page_number - 1),
'pages': paginator.pages,
'hits' : paginator.hits,
+ 'page_range' : paginator.page_range
}, context_processors)
else:
c = RequestContext(request, {
27 docs/generic_views.txt
View
@@ -688,9 +688,8 @@ A page representing a list of objects.
* ``paginate_by``: An integer specifying how many objects should be
displayed per page. If this is given, the view will paginate objects with
``paginate_by`` objects per page. The view will expect either a ``page``
- query string parameter (via ``GET``) containing a 1-based page
- number, or a ``page`` variable specified in the URLconf. See
- "Notes on pagination" below.
+ query string parameter (via ``GET``) or a ``page`` variable specified in
+ the URLconf. See "Notes on pagination" below.
* ``template_name``: The full name of a template to use in rendering the
page. This lets you override the default template name (see below).
@@ -765,6 +764,9 @@ If the results are paginated, the context will contain these extra variables:
* ``hits``: The total number of objects across *all* pages, not just this
page.
+ * ``page_range``: A list of the page numbers that are available. This
+ is 1-based.
+
Notes on pagination
~~~~~~~~~~~~~~~~~~~
@@ -777,12 +779,25 @@ specify the page number in the URL in one of two ways:
(r'^objects/page(?P<page>[0-9]+)/$', 'object_list', dict(info_dict))
* Pass the page number via the ``page`` query-string parameter. For
- example, a URL would look like this:
+ example, a URL would look like this::
/objects/?page=3
-In both cases, ``page`` is 1-based, not 0-based, so the first page would be
-represented as page ``1``.
+ * To loop over all the available page numbers, use the ``page_range``
+ variable. You can iterate over the list provided by ``page_range``
+ to create a link to every page of results.
+
+These values and lists are is 1-based, not 0-based, so the first page would be
+represented as page ``1``. As a special case, you are also permitted to use
+``last`` as a value for ``page``::
+
+ /objects/?page=last
+
+This allows you to access the final page of results without first having to
+determine how many pages there are.
+
+Note that ``page`` *must* be either a valid page number or the value ``last``;
+any other value for ``page`` will result in a 404 error.
``django.views.generic.list_detail.object_detail``
--------------------------------------------------
34 docs/newforms.txt
View
@@ -554,6 +554,29 @@ method you're using::
<p>Sender: <input type="text" name="sender" value="invalid e-mail address" /></p>
<p>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></p>
+Customizing the error list format
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, forms use ``django.newforms.util.ErrorList`` to format validation
+errors. If you'd like to use an alternate class for displaying errors, you can
+pass that in at construction time::
+
+ >>> from django.newforms.util import ErrorList
+ >>> class DivErrorList(ErrorList):
+ ... def __unicode__(self):
+ ... return self.as_divs()
+ ... def as_divs(self):
+ ... if not self: return u''
+ ... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % e for e in self])
+ >>> f = ContactForm(data, auto_id=False, error_class=DivErrorList)
+ >>> f.as_p()
+ <div class="errorlist"><div class="error">This field is required.</div></div>
+ <p>Subject: <input type="text" name="subject" maxlength="100" /></p>
+ <p>Message: <input type="text" name="message" value="Hi there" /></p>
+ <div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
+ <p>Sender: <input type="text" name="sender" value="invalid e-mail address" /></p>
+ <p>Cc myself: <input checked="checked" type="checkbox" name="cc_myself" /></p>
+
More granular output
~~~~~~~~~~~~~~~~~~~~
@@ -1893,6 +1916,17 @@ Note that your callback needs to handle *all* possible model field types, not
just the ones that you want to behave differently to the default. That's why
this example has an ``else`` clause that implements the default behavior.
+.. warning::
+ The field that is passed into the ``formfield_callback`` function in
+ ``form_for_model()`` and ``form_for_instance`` is the field instance from
+ your model's class. You **must not** alter that object at all; treat it
+ as read-only!
+
+ If you make any alterations to that object, it will affect any future
+ users of the model class, because you will have changed the field object
+ used to construct the class. This is almost certainly what you don't want
+ to have happen.
+
Finding the model associated with a form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3  docs/request_response.txt
View
@@ -183,6 +183,9 @@ subclass of dictionary. Exceptions are outlined here:
* ``__getitem__(key)`` -- Returns the value for the given key. If the key
has more than one value, ``__getitem__()`` returns the last value.
+ Raises ``django.utils.datastructure.MultiValueDictKeyError`` if the key
+ does not exist (fortunately, this is a subclass of Python's standard
+ ``KeyError``, so you can stick to catching ``KeyError``).
* ``__setitem__(key, value)`` -- Sets the given key to ``[value]``
(a Python list whose single element is ``value``). Note that this, as
3  docs/sites.txt
View
@@ -316,6 +316,9 @@ Here's how Django uses the sites framework:
* The shortcut view (``django.views.defaults.shortcut``) uses the domain of
the current ``Site`` object when calculating an object's URL.
+ * In the admin framework, the ''view on site'' link uses the current
+ ``Site`` to work out the domain for the site that it will redirect to.
+
.. _redirects framework: ../redirects/
.. _flatpages framework: ../flatpages/
.. _syndication framework: ../syndication_feeds/
25 docs/templates.txt
View
@@ -366,25 +366,36 @@ Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
cycle
~~~~~
-Cycle among the given strings each time this tag is encountered.
+**Changed in Django development version**
+Cycle among the given strings or variables each time this tag is encountered.
-Within a loop, cycles among the given strings each time through the loop::
+Within a loop, cycles among the given strings/variables each time through the
+loop::
{% for o in some_list %}
- <tr class="{% cycle row1,row2 %}">
+ <tr class="{% cycle 'row1' 'row2' rowvar %}">
...
</tr>
{% endfor %}
-
+
Outside of a loop, give the values a unique name the first time you call it,
then use that name each successive time through::
- <tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
+ <tr class="{% cycle 'row1' 'row2' rowvar as rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
-You can use any number of values, separated by commas. Make sure not to put
-spaces between the values -- only commas.
+You can use any number of values, separated by spaces. Values enclosed in
+single (') or double quotes (") are treated as string literals, while values
+without quotes are assumed to refer to context variables.
+
+You can also separate values with commas::
+
+ {% cycle row1,row2,row3 %}
+
+In this syntax, each value will be interpreted as literal text. The
+comma-based syntax exists for backwards-compatibility, and should not be
+used for new projects.
debug
~~~~~
32 docs/templates_python.txt
View
@@ -642,7 +642,23 @@ your function. Example::
"Converts a string into all lowercase"
return value.lower()
-When you've written your filter definition, you need to register it with
+Template filters which expect strings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you're writing a template filter which only expects a string as the first
+argument, you should use the included decorator ``stringfilter``. This will
+convert an object to it's string value before being passed to your function::
+
+ from django.template.defaultfilters import stringfilter
+
+ @stringfilter
+ def lower(value):
+ return value.lower()
+
+Registering a custom filters
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you've written your filter definition, you need to register it with
your ``Library`` instance, to make it available to Django's template language::
register.filter('cut', cut)
@@ -658,28 +674,18 @@ If you're using Python 2.4 or above, you can use ``register.filter()`` as a
decorator instead::
@register.filter(name='cut')
+ @stringfilter
def cut(value, arg):
return value.replace(arg, '')
@register.filter
+ @stringfilter
def lower(value):
return value.lower()
If you leave off the ``name`` argument, as in the second example above, Django
will use the function's name as the filter name.
-Template filters which expect strings
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you are writing a template filter which only expects a string as the first
-argument, you should use the included decorator ``stringfilter`` which will convert
-an object to it's string value before being passed to your function::
-
- from django.template.defaultfilters import stringfilter
-
- @stringfilter
- def lower(value):
- return value.lower()
-
Writing custom template tags
----------------------------
8 docs/testing.txt
View
@@ -569,8 +569,8 @@ Testing responses
The ``get()`` and ``post()`` methods both return a ``Response`` object. This
``Response`` object is *not* the same as the ``HttpResponse`` object returned
-Django views; this object is simpler and has some additional data useful for
-tests.
+Django views; the test response object has some additional data useful for
+test code to verify.
Specifically, a ``Response`` object has the following attributes:
@@ -582,7 +582,7 @@ Specifically, a ``Response`` object has the following attributes:
``content`` The body of the response, as a string. This is the final
page content as rendered by the view, or any error
- message (such as the URL for a 302 redirect).
+ message.
``context`` The template ``Context`` instance that was used to render
the template that produced the response content.
@@ -591,6 +591,8 @@ Specifically, a ``Response`` object has the following attributes:
``context`` will be a list of ``Context``
objects, in the order in which they were rendered.
+ ``headers`` The HTTP headers of the response. This is a dictionary.
+
``request`` The request data that stimulated the response.
``status_code`` The HTTP status of the response, as an integer. See
4 tests/modeltests/pagination/models.py
View
@@ -77,4 +77,8 @@ def __unicode__(self):
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
>>> paginator.pages
2
+
+# The paginator can provide a list of all available pages
+>>> paginator.page_range
+[1, 2]
"""}
290 tests/regressiontests/forms/localflavor.py
View
@@ -1514,4 +1514,294 @@
>>> s = NLProvinceSelect()
>>> s.render('provinces', 'OV')
u'<select name="provinces">\n<option value="DR">Drente</option>\n<option value="FL">Flevoland</option>\n<option value="FR">Friesland</option>\n<option value="GL">Gelderland</option>\n<option value="GR">Groningen</option>\n<option value="LB">Limburg</option>\n<option value="NB">Noord-Brabant</option>\n<option value="NH">Noord-Holland</option>\n<option value="OV" selected="selected">Overijssel</option>\n<option value="UT">Utrecht</option>\n<option value="ZE">Zeeland</option>\n<option value="ZH">Zuid-Holland</option>\n</select>'
+
+# ARProvinceField #############################################################
+
+>>> from django.contrib.localflavor.ar.forms import ARProvinceSelect
+>>> f = ARProvinceSelect()
+>>> f.render('provincias', 'A')
+u'<select name="provincias">\n<option value="B">Buenos Aires</option>\n<option value="K">Catamarca</option>\n<option value="H">Chaco</option>\n<option value="U">Chubut</option>\n<option value="C">Ciudad Aut\xf3noma de Buenos Aires</option>\n<option value="X">C\xf3rdoba</option>\n<option value="W">Corrientes</option>\n<option value="E">Entre R\xedos</option>\n<option value="P">Formosa</option>\n<option value="Y">Jujuy</option>\n<option value="L">La Pampa</option>\n<option value="F">La Rioja</option>\n<option value="M">Mendoza</option>\n<option value="N">Misiones</option>\n<option value="Q">Neuqu\xe9n</option>\n<option value="R">R\xedo Negro</option>\n<option value="A" selected="selected">Salta</option>\n<option value="J">San Juan</option>\n<option value="D">San Luis</option>\n<option value="Z">Santa Cruz</option>\n<option value="S">Santa Fe</option>\n<option value="G">Santiago del Estero</option>\n<option value="V">Tierra del Fuego, Ant\xe1rtida e Islas del Atl\xe1ntico Sur</option>\n<option value="T">Tucum\xe1n</option>\n</select>'
+
+# ARPostalCodeField ###########################################################
+
+>>> from django.contrib.localflavor.ar.forms import ARPostalCodeField
+>>> f = ARPostalCodeField()
+>>> f.clean('5000')
+u'5000'
+>>> f.clean('C1064AAB')
+u'C1064AAB'
+>>> f.clean('c1064AAB')
+u'C1064AAB'
+>>> f.clean('C1064aab')
+u'C1064AAB'
+>>> f.clean(u'4400')
+u'4400'
+>>> f.clean(u'C1064AAB')
+u'C1064AAB'
+>>> f.clean('C1064AABB')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 8 characters (it has 9).']
+>>> f.clean('C1064AA')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean('C106AAB')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean('106AAB')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean('500')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 4 characters (it has 3).']
+>>> f.clean('5PPP')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(u'')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = ARPostalCodeField(required=False)
+>>> f.clean('5000')
+u'5000'
+>>> f.clean('C1064AAB')
+u'C1064AAB'
+>>> f.clean('c1064AAB')
+u'C1064AAB'
+>>> f.clean('C1064aab')
+u'C1064AAB'
+>>> f.clean(u'4400')
+u'4400'
+>>> f.clean(u'C1064AAB')
+u'C1064AAB'
+>>> f.clean('C1064AABB')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 8 characters (it has 9).']
+>>> f.clean('C1064AA')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean('C106AAB')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean('106AAB')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean('500')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 4 characters (it has 3).']
+>>> f.clean('5PPP')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+>>> f.clean(u'')
+u''
+
+# ARDNIField ##################################################################
+
+>>> from django.contrib.localflavor.ar.forms import ARDNIField
+>>> f = ARDNIField()
+>>> f.clean('20123456')
+u'20123456'
+>>> f.clean('20.123.456')
+u'20123456'
+>>> f.clean('9123456')
+u'9123456'
+>>> f.clean('9.123.456')
+u'9123456'
+>>> f.clean(u'20123456')
+u'20123456'
+>>> f.clean(u'20.123.456')
+u'20123456'
+>>> f.clean('20.123456')
+u'20123456'
+>>> f.clean('101234566')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires 7 or 8 digits.']
+>>> f.clean('W0123456')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers.']
+>>> f.clean('10,123,456')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(u'')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = ARDNIField(required=False)
+>>> f.clean('20123456')
+u'20123456'
+>>> f.clean('20.123.456')
+u'20123456'
+>>> f.clean('9123456')
+u'9123456'
+>>> f.clean('9.123.456')
+u'9123456'
+>>> f.clean(u'20123456')
+u'20123456'
+>>> f.clean(u'20.123.456')
+u'20123456'
+>>> f.clean('20.123456')
+u'20123456'
+>>> f.clean('101234566')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires 7 or 8 digits.']
+>>> f.clean('W0123456')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers.']
+>>> f.clean('10,123,456')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+>>> f.clean(u'')
+u''
+
+# ARCUITField #################################################################
+
+>>> from django.contrib.localflavor.ar.forms import ARCUITField
+>>> f = ARCUITField()
+>>> f.clean('20-10123456-9')
+u'20-10123456-9'
+>>> f.clean(u'20-10123456-9')
+u'20-10123456-9'
+>>> f.clean('27-10345678-4')
+u'27-10345678-4'
+>>> f.clean('20101234569')
+u'20-10123456-9'
+>>> f.clean('27103456784')
+u'27-10345678-4'
+>>> f.clean('2-10123456-9')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('210123456-9')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('20-10123456')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('20-10123456-')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('20-10123456-5')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CUIT.']
+>>> f.clean(u'2-10123456-9')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('27-10345678-1')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CUIT.']
+>>> f.clean(u'27-10345678-1')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CUIT.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(u'')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = ARCUITField(required=False)
+>>> f.clean('20-10123456-9')
+u'20-10123456-9'
+>>> f.clean(u'20-10123456-9')
+u'20-10123456-9'
+>>> f.clean('27-10345678-4')
+u'27-10345678-4'
+>>> f.clean('20101234569')
+u'20-10123456-9'
+>>> f.clean('27103456784')
+u'27-10345678-4'
+>>> f.clean('2-10123456-9')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('210123456-9')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('20-10123456')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('20-10123456-')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('20-10123456-5')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CUIT.']
+>>> f.clean(u'2-10123456-9')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+>>> f.clean('27-10345678-1')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CUIT.']
+>>> f.clean(u'27-10345678-1')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CUIT.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+>>> f.clean(u'')
+u''
"""
43 tests/regressiontests/forms/tests.py
View
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from localflavor import localflavor_tests
from regressions import regression_tests
+from util import util_tests
form_tests = r"""
>>> from django.newforms import *
@@ -1606,10 +1607,18 @@
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
+>>> f.clean('http://localhost')
+u'http://localhost'
>>> f.clean('http://example.com')
u'http://example.com'
>>> f.clean('http://www.example.com')
u'http://www.example.com'
+>>> f.clean('http://www.example.com:8000/test')
+u'http://www.example.com:8000/test'
+>>> f.clean('http://200.8.9.10')
+u'http://200.8.9.10'
+>>> f.clean('http://200.8.9.10:8000/test')
+u'http://200.8.9.10:8000/test'
>>> f.clean('foo')
Traceback (most recent call last):
...
@@ -3794,14 +3803,6 @@
>>> smart_unicode('foo')
u'foo'
-# flatatt tests
->>> from django.newforms.util import flatatt
->>> flatatt({'id': "header"})
-u' id="header"'
->>> flatatt({'class': "news", 'title': "Read this"})
-u' class="news" title="Read this"'
->>> flatatt({})
-u''
####################################
# Test accessing errors in clean() #
@@ -3821,12 +3822,38 @@
True
>>> f.cleaned_data['username']
u'sirrobin'
+
+#######################################
+# Test overriding ErrorList in a form #
+#######################################
+
+>>> from django.newforms.util import ErrorList
+>>> class DivErrorList(ErrorList):
+... def __unicode__(self):
+... return self.as_divs()
+... def as_divs(self):
+... if not self: return u''
+... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % e for e in self])
+>>> class CommentForm(Form):
+... name = CharField(max_length=50, required=False)
+... email = EmailField()
+... comment = CharField()
+>>> data = dict(email='invalid')
+>>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
+>>> print f.as_p()
+<p>Name: <input type="text" name="name" maxlength="50" /></p>
+<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
+<p>Email: <input type="text" name="email" value="invalid" /></p>
+<div class="errorlist"><div class="error">This field is required.</div></div>
+<p>Comment: <input type="text" name="comment" /></p>
+
"""
__test__ = {
'form_tests': form_tests,
'localflavor': localflavor_tests,
'regressions': regression_tests,
+ 'util_tests': util_tests,
}
if __name__ == "__main__":
45 tests/regressiontests/forms/util.py
View
@@ -0,0 +1,45 @@
+# coding: utf-8
+"""
+Tests for newforms/util.py module.
+"""
+
+util_tests = r"""
+>>> from django.newforms.util import *
+>>> from django.utils.translation import ugettext_lazy
+
+###########
+# flatatt #
+###########
+
+>>> from django.newforms.util import flatatt
+>>> flatatt({'id': "header"})
+u' id="header"'
+>>> flatatt({'class': "news", 'title': "Read this"})
+u' class="news" title="Read this"'
+>>> flatatt({})
+u''
+
+###################
+# ValidationError #
+###################
+
+# Can take a string.
+>>> print ValidationError("There was an error.").messages
+<ul class="errorlist"><li>There was an error.</li></ul>
+
+# Can take a unicode string.
+>>> print ValidationError(u"Not \u03C0.").messages
+<ul class="errorlist"><li>Not π.</li></ul>
+
+# Can take a lazy string.
+>>> print ValidationError(ugettext_lazy("Error.")).messages
+<ul class="errorlist"><li>Error.</li></ul>
+
+# Can take a list.
+>>> print ValidationError(["Error one.", "Error two."]).messages
+<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
+
+# Can take a mixture in a list.
+>>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
+<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
+"""
8 tests/regressiontests/templates/tests.py
View
@@ -306,6 +306,14 @@ def test_templates(self):
'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
'cycle08': ('{% cycle a,b,c as foo %}{% cycle foo %}{{ foo }}{{ foo }}{% cycle foo %}{{ foo }}', {}, 'abbbcc'),
+ 'cycle09': ("{% for i in test %}{% cycle a,b %}{{ i }},{% endfor %}", {'test': range(5)}, 'a0,b1,a2,b3,a4,'),
+ # New format:
+ 'cycle10': ("{% cycle 'a' 'b' 'c' as abc %}{% cycle abc %}", {}, 'ab'),
+ 'cycle11': ("{% cycle 'a' 'b' 'c' as abc %}{% cycle abc %}{% cycle abc %}", {}, 'abc'),
+ 'cycle12': ("{% cycle 'a' 'b' 'c' as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}", {}, 'abca'),
+ 'cycle13': ("{% for i in test %}{% cycle 'a' 'b' %}{{ i }},{% endfor %}", {'test': range(5)}, 'a0,b1,a2,b3,a4,'),
+ 'cycle14': ("{% cycle one two as foo %}{% cycle foo %}", {'one': '1','two': '2'}, '12'),
+ 'cycle13': ("{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}", {'test': range(5), 'aye': 'a', 'bee': 'b'}, 'a0,b1,a2,b3,a4,'),
### EXCEPTIONS ############################################################
Please sign in to comment.
Something went wrong with that request. Please try again.