1
0
mirror of https://github.com/django/django.git synced 2025-07-06 18:59:13 +00:00

queryset-refactor: Merged to [6155]

git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6332 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2007-09-15 21:21:37 +00:00
parent c8f6e485b8
commit fb6a0c8ffa
37 changed files with 717 additions and 100 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ class ObjectPaginator(object):
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:
@ -84,5 +85,15 @@ class ObjectPaginator(object):
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)

View File

@ -855,6 +855,7 @@ class ImageField(FileField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.ImageField}
defaults.update(kwargs)
return super(ImageField, self).formfield(**defaults)
class IntegerField(Field):

View File

@ -335,12 +335,6 @@ class EmailField(RegexField):
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 @@ class ImageField(FileField):
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):

View File

@ -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 @@ class BaseForm(StrAndUnicode):
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 @@ class BaseForm(StrAndUnicode):
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 @@ class BoundField(StrAndUnicode):
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):

View File

@ -30,6 +30,7 @@ class CycleNode(Node):
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):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -366,12 +366,14 @@ 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 %}
@ -379,12 +381,21 @@ Within a loop, cycles among the given strings each time through the loop::
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
~~~~~

View File

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

View File

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

View File

@ -77,4 +77,8 @@ True
>>> 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]
"""}

View File

@ -1514,4 +1514,294 @@ ValidationError: [u'Enter a valid SoFi number']
>>> 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''
"""

View File

@ -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 @@ ValidationError: [u'This field is required.']
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 @@ u'1'
>>> 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 @@ u''
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__":

View File

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

View File

@ -306,6 +306,14 @@ class Templates(unittest.TestCase):
'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 ############################################################