1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

boulder-oracle-sprint: Merged to [4065]

git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4066 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Boulder Sprinters 2006-11-13 00:25:50 +00:00
parent 2ef60b546b
commit 19773e0d70
41 changed files with 718 additions and 275 deletions

View File

@ -101,6 +101,7 @@ DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3. DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
# Host for sending e-mail. # Host for sending e-mail.
EMAIL_HOST = 'localhost' EMAIL_HOST = 'localhost'
@ -228,6 +229,10 @@ MONTH_DAY_FORMAT = 'F j'
# Hint: you really don't! # Hint: you really don't!
TRANSACTIONS_MANAGED = False TRANSACTIONS_MANAGED = False
# The User-Agent string to use when checking for URL validity through the
# isExistingURL validator.
URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)"
############## ##############
# MIDDLEWARE # # MIDDLEWARE #
############## ##############

View File

@ -169,8 +169,8 @@ var dateParsePatterns = [
handler: function(bits) { handler: function(bits) {
var d = new Date(); var d = new Date();
d.setYear(parseInt(bits[1])); d.setYear(parseInt(bits[1]));
d.setDate(parseInt(bits[3], 10));
d.setMonth(parseInt(bits[2], 10) - 1); d.setMonth(parseInt(bits[2], 10) - 1);
d.setDate(parseInt(bits[3], 10));
return d; return d;
} }
}, },

View File

@ -177,8 +177,8 @@ def output_all(form_fields):
output_all = register.simple_tag(output_all) output_all = register.simple_tag(output_all)
def auto_populated_field_script(auto_pop_fields, change = False): def auto_populated_field_script(auto_pop_fields, change = False):
t = []
for field in auto_pop_fields: for field in auto_pop_fields:
t = []
if change: if change:
t.append('document.getElementById("id_%s")._changed = true;' % field.name) t.append('document.getElementById("id_%s")._changed = true;' % field.name)
else: else:

View File

@ -34,7 +34,7 @@ class CommentManager(models.Manager):
""" """
Given a rating_string, this returns a tuple of (rating_range, options). Given a rating_string, this returns a tuple of (rating_range, options).
>>> s = "scale:1-10|First_category|Second_category" >>> s = "scale:1-10|First_category|Second_category"
>>> get_rating_options(s) >>> Comment.objects.get_rating_options(s)
([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category']) ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
""" """
rating_range, options = rating_string.split('|', 1) rating_range, options = rating_string.split('|', 1)

View File

@ -4,6 +4,11 @@ from django.conf import settings
from email.MIMEText import MIMEText from email.MIMEText import MIMEText
from email.Header import Header from email.Header import Header
import smtplib, rfc822 import smtplib, rfc822
import socket
import time
import random
DNS_NAME = socket.getfqdn() # Cache the hostname
class BadHeaderError(ValueError): class BadHeaderError(ValueError):
pass pass
@ -50,6 +55,11 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=settings.EMAIL_HOST
msg['From'] = from_email msg['From'] = from_email
msg['To'] = ', '.join(recipient_list) msg['To'] = ', '.join(recipient_list)
msg['Date'] = rfc822.formatdate() msg['Date'] = rfc822.formatdate()
try:
random_bits = str(random.getrandbits(64))
except AttributeError: # Python 2.3 doesn't have random.getrandbits().
random_bits = ''.join([random.choice('1234567890') for i in range(19)])
msg['Message-ID'] = "<%d.%s@%s>" % (time.time(), random_bits, DNS_NAME)
try: try:
server.sendmail(from_email, recipient_list, msg.as_string()) server.sendmail(from_email, recipient_list, msg.as_string())
num_sent += 1 num_sent += 1

View File

@ -393,6 +393,8 @@ def get_sql_initial_data_for_model(model):
if os.path.exists(sql_file): if os.path.exists(sql_file):
fp = open(sql_file, 'U') fp = open(sql_file, 'U')
for statement in statements.split(fp.read()): for statement in statements.split(fp.read()):
# Remove any comments from the file
statement = re.sub(r"--.*[\n\Z]", "", statement)
if statement.strip(): if statement.strip():
output.append(statement + ";") output.append(statement + ";")
fp.close() fp.close()

View File

@ -1,54 +1,46 @@
from math import ceil
class InvalidPage(Exception): class InvalidPage(Exception):
pass pass
class ObjectPaginator(object): class ObjectPaginator(object):
""" """
This class makes pagination easy. Feed it a QuerySet, plus the number of This class makes pagination easy. Feed it a QuerySet or list, plus the number
objects you want on each page. Then read the hits and pages properties to of objects you want on each page. Then read the hits and pages properties to
see how many pages it involves. Call get_page with a page number (starting see how many pages it involves. Call get_page with a page number (starting
at 0) to get back a list of objects for that page. at 0) to get back a list of objects for that page.
Finally, check if a page number has a next/prev page using Finally, check if a page number has a next/prev page using
has_next_page(page_number) and has_previous_page(page_number). has_next_page(page_number) and has_previous_page(page_number).
Use orphans to avoid small final pages. For example:
13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10
12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12
""" """
def __init__(self, query_set, num_per_page): def __init__(self, query_set, num_per_page, orphans=0):
self.query_set = query_set self.query_set = query_set
self.num_per_page = num_per_page self.num_per_page = num_per_page
self._hits, self._pages = None, None self.orphans = orphans
self._has_next = {} # Caches page_number -> has_next_boolean self._hits = self._pages = None
def get_page(self, page_number): def validate_page_number(self, page_number):
try: try:
page_number = int(page_number) page_number = int(page_number)
except ValueError: except ValueError:
raise InvalidPage raise InvalidPage
if page_number < 0: if page_number < 0 or page_number > self.pages - 1:
raise InvalidPage raise InvalidPage
return page_number
# Retrieve one extra record, and check for the existence of that extra def get_page(self, page_number):
# record to determine whether there's a next page. page_number = self.validate_page_number(page_number)
limit = self.num_per_page + 1 bottom = page_number * self.num_per_page
offset = page_number * self.num_per_page top = bottom + self.num_per_page
if top + self.orphans >= self.hits:
object_list = list(self.query_set[offset:offset+limit]) top = self.hits
return self.query_set[bottom:top]
if not object_list:
raise InvalidPage
self._has_next[page_number] = (len(object_list) > self.num_per_page)
return object_list[:self.num_per_page]
def has_next_page(self, page_number): def has_next_page(self, page_number):
"Does page $page_number have a 'next' page?" "Does page $page_number have a 'next' page?"
if not self._has_next.has_key(page_number): return page_number < self.pages - 1
if self._pages is None:
offset = (page_number + 1) * self.num_per_page
self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0
else:
self._has_next[page_number] = page_number < (self.pages - 1)
return self._has_next[page_number]
def has_previous_page(self, page_number): def has_previous_page(self, page_number):
return page_number > 0 return page_number > 0
@ -58,8 +50,7 @@ class ObjectPaginator(object):
Returns the 1-based index of the first object on the given page, Returns the 1-based index of the first object on the given page,
relative to total objects found (hits). relative to total objects found (hits).
""" """
if page_number == 0: page_number = self.validate_page_number(page_number)
return 1
return (self.num_per_page * page_number) + 1 return (self.num_per_page * page_number) + 1
def last_on_page(self, page_number): def last_on_page(self, page_number):
@ -67,20 +58,30 @@ class ObjectPaginator(object):
Returns the 1-based index of the last object on the given page, Returns the 1-based index of the last object on the given page,
relative to total objects found (hits). relative to total objects found (hits).
""" """
if page_number == 0 and self.num_per_page >= self._hits: page_number = self.validate_page_number(page_number)
return self._hits page_number += 1 # 1-base
elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits: if page_number == self.pages:
return self._hits return self.hits
return (page_number + 1) * self.num_per_page return page_number * self.num_per_page
def _get_hits(self): def _get_hits(self):
if self._hits is None: if self._hits is None:
self._hits = self.query_set.count() # Try .count() or fall back to len().
try:
self._hits = int(self.query_set.count())
except (AttributeError, TypeError, ValueError):
# AttributeError if query_set has no object count.
# TypeError if query_set.count() required arguments.
# ValueError if int() fails.
self._hits = len(self.query_set)
return self._hits return self._hits
def _get_pages(self): def _get_pages(self):
if self._pages is None: if self._pages is None:
self._pages = int(ceil(self.hits / float(self.num_per_page))) hits = (self.hits - 1 - self.orphans)
if hits < 1:
hits = 0
self._pages = hits // self.num_per_page + 1
return self._pages return self._pages
hits = property(_get_hits) hits = property(_get_hits)

View File

@ -28,6 +28,7 @@ class Serializer(object):
self.options = options self.options = options
self.stream = options.get("stream", StringIO()) self.stream = options.get("stream", StringIO())
self.selected_fields = options.get("fields")
self.start_serialization() self.start_serialization()
for obj in queryset: for obj in queryset:
@ -36,11 +37,14 @@ class Serializer(object):
if field is obj._meta.pk: if field is obj._meta.pk:
continue continue
elif field.rel is None: elif field.rel is None:
self.handle_field(obj, field) if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_field(obj, field)
else: else:
self.handle_fk_field(obj, field) if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
self.handle_fk_field(obj, field)
for field in obj._meta.many_to_many: for field in obj._meta.many_to_many:
self.handle_m2m_field(obj, field) if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_m2m_field(obj, field)
self.end_object(obj) self.end_object(obj)
self.end_serialization() self.end_serialization()
return self.getvalue() return self.getvalue()

View File

@ -76,7 +76,7 @@ def Deserializer(object_list, **options):
m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values() m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values()
# Handle FK fields # Handle FK fields
elif field.rel and isinstance(field.rel, models.ManyToOneRel): elif field.rel and isinstance(field.rel, models.ManyToOneRel) and field_value is not None:
try: try:
data[field.name] = field.rel.to._default_manager.get(pk=field_value) data[field.name] = field.rel.to._default_manager.get(pk=field_value)
except field.rel.to.DoesNotExist: except field.rel.to.DoesNotExist:

View File

@ -166,7 +166,11 @@ class Deserializer(base.Deserializer):
# If it doesn't exist, set the field to None (which might trigger # If it doesn't exist, set the field to None (which might trigger
# validation error, but that's expected). # validation error, but that's expected).
RelatedModel = self._get_model_from_node(node, "to") RelatedModel = self._get_model_from_node(node, "to")
return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding)) # Check if there is a child node named 'None', returning None if so.
if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None':
return None
else:
return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
def _handle_m2m_field_node(self, node): def _handle_m2m_field_node(self, node):
""" """

View File

@ -33,9 +33,9 @@ Optional Fcgi settings: (setting=value)
method=IMPL prefork or threaded (default prefork) method=IMPL prefork or threaded (default prefork)
maxrequests=NUMBER number of requests a child handles before it is maxrequests=NUMBER number of requests a child handles before it is
killed and a new child is forked (0 = no limit). killed and a new child is forked (0 = no limit).
maxspare=NUMBER max number of spare processes to keep running. maxspare=NUMBER max number of spare processes / threads
minspare=NUMBER min number of spare processes to prefork. minspare=NUMBER min number of spare processes / threads.
maxchildren=NUMBER hard limit number of processes in prefork mode. maxchildren=NUMBER hard limit number of processes / threads
daemonize=BOOL whether to detach from terminal. daemonize=BOOL whether to detach from terminal.
pidfile=FILE write the spawned process-id to this file. pidfile=FILE write the spawned process-id to this file.
workdir=DIRECTORY change to this directory when daemonizing workdir=DIRECTORY change to this directory when daemonizing
@ -110,7 +110,11 @@ def runfastcgi(argset=[], **kwargs):
} }
elif options['method'] in ('thread', 'threaded'): elif options['method'] in ('thread', 'threaded'):
from flup.server.fcgi import WSGIServer from flup.server.fcgi import WSGIServer
wsgi_opts = {} wsgi_opts = {
'maxSpare': int(options["maxspare"]),
'minSpare': int(options["minspare"]),
'maxThreads': int(options["maxchildren"]),
}
else: else:
return fastcgi_help("ERROR: Implementation must be one of prefork or thread.") return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")

View File

@ -21,7 +21,10 @@ class NoReverseMatch(Exception):
def get_mod_func(callback): def get_mod_func(callback):
# Converts 'django.views.news.stories.story_detail' to # Converts 'django.views.news.stories.story_detail' to
# ['django.views.news.stories', 'story_detail'] # ['django.views.news.stories', 'story_detail']
dot = callback.rindex('.') try:
dot = callback.rindex('.')
except ValueError:
return callback, ''
return callback[:dot], callback[dot+1:] return callback[:dot], callback[dot+1:]
def reverse_helper(regex, *args, **kwargs): def reverse_helper(regex, *args, **kwargs):

View File

@ -8,6 +8,7 @@ validator will *always* be run, regardless of whether its associated
form field is required. form field is required.
""" """
import urllib2
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext, gettext_lazy, ngettext from django.utils.translation import gettext, gettext_lazy, ngettext
from django.utils.functional import Promise, lazy from django.utils.functional import Promise, lazy
@ -223,18 +224,26 @@ def isWellFormedXmlFragment(field_data, all_data):
isWellFormedXml('<root>%s</root>' % field_data, all_data) isWellFormedXml('<root>%s</root>' % field_data, all_data)
def isExistingURL(field_data, all_data): def isExistingURL(field_data, all_data):
import urllib2
try: try:
u = urllib2.urlopen(field_data) headers = {
"Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
"Accept-Language" : "en-us,en;q=0.5",
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
"Connection" : "close",
"User-Agent": settings.URL_VALIDATOR_USER_AGENT
}
req = urllib2.Request(field_data,None, headers)
u = urllib2.urlopen(req)
except ValueError: except ValueError:
raise ValidationError, gettext("Invalid URL: %s") % field_data raise ValidationError, _("Invalid URL: %s") % field_data
except urllib2.HTTPError, e: except urllib2.HTTPError, e:
# 401s are valid; they just mean authorization is required. # 401s are valid; they just mean authorization is required.
if e.code not in ('401',): # 301 and 302 are redirects; they just mean look somewhere else.
raise ValidationError, gettext("The URL %s is a broken link.") % field_data if str(e.code) not in ('401','301','302'):
raise ValidationError, _("The URL %s is a broken link.") % field_data
except: # urllib2.URLError, httplib.InvalidURL, etc. except: # urllib2.URLError, httplib.InvalidURL, etc.
raise ValidationError, gettext("The URL %s is a broken link.") % field_data raise ValidationError, _("The URL %s is a broken link.") % field_data
def isValidUSState(field_data, all_data): def isValidUSState(field_data, all_data):
"Checks that the given string is a valid two-letter U.S. state abbreviation" "Checks that the given string is a valid two-letter U.S. state abbreviation"
states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY'] states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
@ -344,6 +353,38 @@ class UniqueAmongstFieldsWithPrefix(object):
if field_name != self.field_name and value == field_data: if field_name != self.field_name and value == field_data:
raise ValidationError, self.error_message raise ValidationError, self.error_message
class NumberIsInRange(object):
"""
Validator that tests if a value is in a range (inclusive).
"""
def __init__(self, lower=None, upper=None, error_message=''):
self.lower, self.upper = lower, upper
if not error_message:
if lower and upper:
self.error_message = gettext("This value must be between %s and %s.") % (lower, upper)
elif lower:
self.error_message = gettext("This value must be at least %s.") % lower
elif upper:
self.error_message = gettext("This value must be no more than %s.") % upper
else:
self.error_message = error_message
def __call__(self, field_data, all_data):
# Try to make the value numeric. If this fails, we assume another
# validator will catch the problem.
try:
val = float(field_data)
except ValueError:
return
# Now validate
if self.lower and self.upper and (val < self.lower or val > self.upper):
raise ValidationError(self.error_message)
elif self.lower and val < self.lower:
raise ValidationError(self.error_message)
elif self.upper and val > self.upper:
raise ValidationError(self.error_message)
class IsAPowerOf(object): class IsAPowerOf(object):
""" """
>>> v = IsAPowerOf(2) >>> v = IsAPowerOf(2)

View File

@ -35,7 +35,8 @@ get_query_module = backend_module_accessor("query")
get_client_module = backend_module_accessor("client") get_client_module = backend_module_accessor("client")
runshell = lambda: get_client_module().runshell() runshell = lambda: get_client_module().runshell()
connection = backend.DatabaseWrapper() connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
DatabaseError = backend.DatabaseError DatabaseError = backend.DatabaseError
# Register an event that closes the database connection # Register an event that closes the database connection

View File

@ -55,7 +55,7 @@ except ImportError:
from django.utils._threading_local import local from django.utils._threading_local import local
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self): def __init__(self, **kwargs):
self.connection = None self.connection = None
self.queries = [] self.queries = []

View File

@ -20,6 +20,9 @@ class DatabaseWrapper:
_commit = complain _commit = complain
_rollback = complain _rollback = complain
def __init__(self, **kwargs):
pass
def close(self): def close(self):
pass # close() pass # close()

View File

@ -65,10 +65,11 @@ except ImportError:
from django.utils._threading_local import local from django.utils._threading_local import local
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self): def __init__(self, **kwargs):
self.connection = None self.connection = None
self.queries = [] self.queries = []
self.server_version = None self.server_version = None
self.options = kwargs
def _valid_connection(self): def _valid_connection(self):
if self.connection is not None: if self.connection is not None:
@ -95,6 +96,7 @@ class DatabaseWrapper(local):
kwargs['host'] = settings.DATABASE_HOST kwargs['host'] = settings.DATABASE_HOST
if settings.DATABASE_PORT: if settings.DATABASE_PORT:
kwargs['port'] = int(settings.DATABASE_PORT) kwargs['port'] = int(settings.DATABASE_PORT)
kwargs.update(self.options)
self.connection = Database.connect(**kwargs) self.connection = Database.connect(**kwargs)
cursor = self.connection.cursor() cursor = self.connection.cursor()
if self.connection.get_server_info() >= '4.1': if self.connection.get_server_info() >= '4.1':
@ -180,6 +182,9 @@ def get_drop_foreignkey_sql():
def get_pk_default_value(): def get_pk_default_value():
return "DEFAULT" return "DEFAULT"
def get_max_name_length():
return 64;
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',
'iexact': 'LIKE %s', 'iexact': 'LIKE %s',

View File

@ -21,9 +21,10 @@ except ImportError:
from django.utils._threading_local import local from django.utils._threading_local import local
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self): def __init__(self, **kwargs):
self.connection = None self.connection = None
self.queries = [] self.queries = []
self.options = kwargs
def _valid_connection(self): def _valid_connection(self):
return self.connection is not None return self.connection is not None
@ -35,10 +36,10 @@ class DatabaseWrapper(local):
settings.DATABASE_HOST = 'localhost' settings.DATABASE_HOST = 'localhost'
if len(settings.DATABASE_PORT.strip()) != 0: if len(settings.DATABASE_PORT.strip()) != 0:
dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME) dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME)
self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn) self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn, **self.options)
else: else:
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
self.connection = Database.connect(conn_string) self.connection = Database.connect(conn_string, **self.options)
# set oracle date to ansi date format # set oracle date to ansi date format
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'") cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'")

View File

@ -21,9 +21,10 @@ except ImportError:
from django.utils._threading_local import local from django.utils._threading_local import local
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self): def __init__(self, **kwargs):
self.connection = None self.connection = None
self.queries = [] self.queries = []
self.options = kwargs
def cursor(self): def cursor(self):
from django.conf import settings from django.conf import settings
@ -40,7 +41,7 @@ class DatabaseWrapper(local):
conn_string += " host=%s" % settings.DATABASE_HOST conn_string += " host=%s" % settings.DATABASE_HOST
if settings.DATABASE_PORT: if settings.DATABASE_PORT:
conn_string += " port=%s" % settings.DATABASE_PORT conn_string += " port=%s" % settings.DATABASE_PORT
self.connection = Database.connect(conn_string) self.connection = Database.connect(conn_string, **self.options)
self.connection.set_isolation_level(1) # make transactions transparent to all cursors self.connection.set_isolation_level(1) # make transactions transparent to all cursors
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])

View File

@ -21,9 +21,10 @@ except ImportError:
from django.utils._threading_local import local from django.utils._threading_local import local
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self): def __init__(self, **kwargs):
self.connection = None self.connection = None
self.queries = [] self.queries = []
self.options = kwargs
def cursor(self): def cursor(self):
from django.conf import settings from django.conf import settings
@ -40,7 +41,7 @@ class DatabaseWrapper(local):
conn_string += " host=%s" % settings.DATABASE_HOST conn_string += " host=%s" % settings.DATABASE_HOST
if settings.DATABASE_PORT: if settings.DATABASE_PORT:
conn_string += " port=%s" % settings.DATABASE_PORT conn_string += " port=%s" % settings.DATABASE_PORT
self.connection = Database.connect(conn_string) self.connection = Database.connect(conn_string, **self.options)
self.connection.set_isolation_level(1) # make transactions transparent to all cursors self.connection.set_isolation_level(1) # make transactions transparent to all cursors
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.tzinfo_factory = None cursor.tzinfo_factory = None

View File

@ -42,16 +42,20 @@ except ImportError:
from django.utils._threading_local import local from django.utils._threading_local import local
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self): def __init__(self, **kwargs):
self.connection = None self.connection = None
self.queries = [] self.queries = []
self.options = kwargs
def cursor(self): def cursor(self):
from django.conf import settings from django.conf import settings
if self.connection is None: if self.connection is None:
self.connection = Database.connect(settings.DATABASE_NAME, kwargs = {
detect_types=Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES) 'database': settings.DATABASE_NAME,
'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
}
kwargs.update(self.options)
self.connection = Database.connect(**kwargs)
# Register extract and date_trunc functions. # Register extract and date_trunc functions.
self.connection.create_function("django_extract", 2, _sqlite_extract) self.connection.create_function("django_extract", 2, _sqlite_extract)
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)

View File

@ -18,7 +18,7 @@ class CursorDebugWrapper(object):
if not isinstance(params, (tuple, dict)): if not isinstance(params, (tuple, dict)):
params = tuple(params) params = tuple(params)
self.db.queries.append({ self.db.queries.append({
'sql': sql % tuple(params), 'sql': sql % params,
'time': "%.3f" % (stop - start), 'time': "%.3f" % (stop - start),
}) })

View File

@ -605,7 +605,7 @@ class FileField(Field):
# If the raw path is passed in, validate it's under the MEDIA_ROOT. # If the raw path is passed in, validate it's under the MEDIA_ROOT.
def isWithinMediaRoot(field_data, all_data): def isWithinMediaRoot(field_data, all_data):
f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data)) f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data))
if not f.startswith(os.path.normpath(settings.MEDIA_ROOT)): if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))):
raise validators.ValidationError, _("Enter a valid filename.") raise validators.ValidationError, _("Enter a valid filename.")
field_list[1].validator_list.append(isWithinMediaRoot) field_list[1].validator_list.append(isWithinMediaRoot)
return field_list return field_list

View File

@ -286,7 +286,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat
# This is really not going to work for fields that have different # This is really not going to work for fields that have different
# form fields, e.g. DateTime. # form fields, e.g. DateTime.
# This validation needs to occur after html2python to be effective. # This validation needs to occur after html2python to be effective.
field_val = all_data.get(f.attname, None) field_val = all_data.get(f.name, None)
if field_val is None: if field_val is None:
# This will be caught by another validator, assuming the field # This will be caught by another validator, assuming the field
# doesn't have blank=True. # doesn't have blank=True.

View File

@ -170,7 +170,6 @@ class _QuerySet(object):
cursor = connection.cursor() cursor = connection.cursor()
full_query = None
select, sql, params, full_query = self._get_sql_clause() select, sql, params, full_query = self._get_sql_clause()
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)

View File

@ -108,8 +108,13 @@ class FormWrapper(object):
This allows dictionary-style lookups of formfields. It also handles feeding This allows dictionary-style lookups of formfields. It also handles feeding
prepopulated data and validation error messages to the formfield objects. prepopulated data and validation error messages to the formfield objects.
""" """
def __init__(self, manipulator, data, error_dict, edit_inline=True): def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True):
self.manipulator, self.data = manipulator, data self.manipulator = manipulator
if data is None:
data = {}
if error_dict is None:
error_dict = {}
self.data = data
self.error_dict = error_dict self.error_dict = error_dict
self._inline_collections = None self._inline_collections = None
self.edit_inline = edit_inline self.edit_inline = edit_inline

View File

@ -2,7 +2,6 @@
Django validation and HTML form handling. Django validation and HTML form handling.
TODO: TODO:
Validation not tied to a particular field
Default value for field Default value for field
Field labels Field labels
Nestable Forms Nestable Forms
@ -11,6 +10,7 @@ TODO:
"This form field requires foo.js" and form.js_includes() "This form field requires foo.js" and form.js_includes()
""" """
from util import ValidationError
from widgets import * from widgets import *
from fields import * from fields import *
from forms import Form from forms import Form

View File

@ -14,6 +14,7 @@ __all__ = (
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'URLField', 'BooleanField', 'RegexField', 'EmailField', 'URLField', 'BooleanField',
'ChoiceField', 'MultipleChoiceField', 'ChoiceField', 'MultipleChoiceField',
'ComboField',
) )
# These values, if given to to_python(), will trigger the self.required check. # These values, if given to to_python(), will trigger the self.required check.
@ -34,9 +35,9 @@ class Field(object):
widget = widget() widget = widget()
self.widget = widget self.widget = widget
def to_python(self, value): def clean(self, value):
""" """
Validates the given value and returns its "normalized" value as an Validates the given value and returns its "cleaned" value as an
appropriate Python object. appropriate Python object.
Raises ValidationError for any errors. Raises ValidationError for any errors.
@ -50,9 +51,9 @@ class CharField(Field):
Field.__init__(self, required, widget) Field.__init__(self, required, widget)
self.max_length, self.min_length = max_length, min_length self.max_length, self.min_length = max_length, min_length
def to_python(self, value): def clean(self, value):
"Validates max_length and min_length. Returns a Unicode object." "Validates max_length and min_length. Returns a Unicode object."
Field.to_python(self, value) Field.clean(self, value)
if value in EMPTY_VALUES: value = u'' if value in EMPTY_VALUES: value = u''
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = unicode(str(value), DEFAULT_ENCODING) value = unicode(str(value), DEFAULT_ENCODING)
@ -65,12 +66,12 @@ class CharField(Field):
return value return value
class IntegerField(Field): class IntegerField(Field):
def to_python(self, value): def clean(self, value):
""" """
Validates that int() can be called on the input. Returns the result Validates that int() can be called on the input. Returns the result
of int(). of int().
""" """
super(IntegerField, self).to_python(value) super(IntegerField, self).clean(value)
try: try:
return int(value) return int(value)
except (ValueError, TypeError): except (ValueError, TypeError):
@ -89,12 +90,12 @@ class DateField(Field):
Field.__init__(self, required, widget) Field.__init__(self, required, widget)
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
def to_python(self, value): def clean(self, value):
""" """
Validates that the input can be converted to a date. Returns a Python Validates that the input can be converted to a date. Returns a Python
datetime.date object. datetime.date object.
""" """
Field.to_python(self, value) Field.clean(self, value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return None return None
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
@ -125,12 +126,12 @@ class DateTimeField(Field):
Field.__init__(self, required, widget) Field.__init__(self, required, widget)
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
def to_python(self, value): def clean(self, value):
""" """
Validates that the input can be converted to a datetime. Returns a Validates that the input can be converted to a datetime. Returns a
Python datetime.datetime object. Python datetime.datetime object.
""" """
Field.to_python(self, value) Field.clean(self, value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return None return None
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
@ -157,12 +158,12 @@ class RegexField(Field):
self.regex = regex self.regex = regex
self.error_message = error_message or u'Enter a valid value.' self.error_message = error_message or u'Enter a valid value.'
def to_python(self, value): def clean(self, value):
""" """
Validates that the input matches the regular expression. Returns a Validates that the input matches the regular expression. Returns a
Unicode object. Unicode object.
""" """
Field.to_python(self, value) Field.clean(self, value)
if value in EMPTY_VALUES: value = u'' if value in EMPTY_VALUES: value = u''
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = unicode(str(value), DEFAULT_ENCODING) value = unicode(str(value), DEFAULT_ENCODING)
@ -187,17 +188,35 @@ url_re = re.compile(
r'(?::\d+)?' # optional port r'(?::\d+)?' # optional port
r'(?:/?|/\S+)$', re.IGNORECASE) r'(?:/?|/\S+)$', re.IGNORECASE)
try:
from django.conf import settings
URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
except ImportError:
# It's OK if Django settings aren't configured.
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
class URLField(RegexField): class URLField(RegexField):
def __init__(self, required=True, verify_exists=False, widget=None): def __init__(self, required=True, verify_exists=False, widget=None,
validator_user_agent=URL_VALIDATOR_USER_AGENT):
RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget) RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget)
self.verify_exists = verify_exists self.verify_exists = verify_exists
self.user_agent = validator_user_agent
def to_python(self, value): def clean(self, value):
value = RegexField.to_python(self, value) value = RegexField.clean(self, value)
if self.verify_exists: if self.verify_exists:
import urllib2 import urllib2
from django.conf import settings
headers = {
"Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
"Accept-Language": "en-us,en;q=0.5",
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
"Connection": "close",
"User-Agent": self.user_agent,
}
try: try:
u = urllib2.urlopen(value) req = urllib2.Request(value, None, headers)
u = urllib2.urlopen(req)
except ValueError: except ValueError:
raise ValidationError(u'Enter a valid URL.') raise ValidationError(u'Enter a valid URL.')
except: # urllib2.URLError, httplib.InvalidURL, etc. except: # urllib2.URLError, httplib.InvalidURL, etc.
@ -207,9 +226,9 @@ class URLField(RegexField):
class BooleanField(Field): class BooleanField(Field):
widget = CheckboxInput widget = CheckboxInput
def to_python(self, value): def clean(self, value):
"Returns a Python boolean object." "Returns a Python boolean object."
Field.to_python(self, value) Field.clean(self, value)
return bool(value) return bool(value)
class ChoiceField(Field): class ChoiceField(Field):
@ -219,11 +238,11 @@ class ChoiceField(Field):
Field.__init__(self, required, widget) Field.__init__(self, required, widget)
self.choices = choices self.choices = choices
def to_python(self, value): def clean(self, value):
""" """
Validates that the input is in self.choices. Validates that the input is in self.choices.
""" """
value = Field.to_python(self, value) value = Field.clean(self, value)
if value in EMPTY_VALUES: value = u'' if value in EMPTY_VALUES: value = u''
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = unicode(str(value), DEFAULT_ENCODING) value = unicode(str(value), DEFAULT_ENCODING)
@ -238,7 +257,7 @@ class MultipleChoiceField(ChoiceField):
def __init__(self, choices=(), required=True, widget=SelectMultiple): def __init__(self, choices=(), required=True, widget=SelectMultiple):
ChoiceField.__init__(self, choices, required, widget) ChoiceField.__init__(self, choices, required, widget)
def to_python(self, value): def clean(self, value):
""" """
Validates that the input is a list or tuple. Validates that the input is a list or tuple.
""" """
@ -259,3 +278,18 @@ class MultipleChoiceField(ChoiceField):
if val not in valid_values: if val not in valid_values:
raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % val) raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % val)
return new_value return new_value
class ComboField(Field):
def __init__(self, fields=(), required=True, widget=None):
Field.__init__(self, required, widget)
self.fields = fields
def clean(self, value):
"""
Validates the given value against all of self.fields, which is a
list of Field instances.
"""
Field.clean(self, value)
for field in self.fields:
value = field.clean(value)
return value

View File

@ -6,6 +6,13 @@ from fields import Field
from widgets import TextInput, Textarea from widgets import TextInput, Textarea
from util import ErrorDict, ErrorList, ValidationError from util import ErrorDict, ErrorList, ValidationError
NON_FIELD_ERRORS = '__all__'
def pretty_name(name):
"Converts 'first_name' to 'First name'"
name = name[0].upper() + name[1:]
return name.replace('_', ' ')
class DeclarativeFieldsMetaclass(type): class DeclarativeFieldsMetaclass(type):
"Metaclass that converts Field attributes to a dictionary called 'fields'." "Metaclass that converts Field attributes to a dictionary called 'fields'."
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
@ -18,22 +25,33 @@ class Form(object):
def __init__(self, data=None): # TODO: prefix stuff def __init__(self, data=None): # TODO: prefix stuff
self.data = data or {} self.data = data or {}
self.__data_python = None # Stores the data after to_python() has been called. self.clean_data = None # Stores the data after clean() has been called.
self.__errors = None # Stores the errors after to_python() has been called. self.__errors = None # Stores the errors after clean() has been called.
def __str__(self):
return self.as_table()
def __iter__(self): def __iter__(self):
for name, field in self.fields.items(): for name, field in self.fields.items():
yield BoundField(self, field, name) yield BoundField(self, field, name)
def to_python(self): def __getitem__(self, name):
"Returns a BoundField with the given name."
try:
field = self.fields[name]
except KeyError:
raise KeyError('Key %r not found in Form' % name)
return BoundField(self, field, name)
def clean(self):
if self.__errors is None: if self.__errors is None:
self._validate() self.full_clean()
return self.__data_python return self.clean_data
def errors(self): def errors(self):
"Returns an ErrorDict for self.data" "Returns an ErrorDict for self.data"
if self.__errors is None: if self.__errors is None:
self._validate() self.full_clean()
return self.__errors return self.__errors
def is_valid(self): def is_valid(self):
@ -44,27 +62,75 @@ class Form(object):
""" """
return not bool(self.errors()) return not bool(self.errors())
def __getitem__(self, name): def as_table(self):
"Returns a BoundField with the given name." "Returns this form rendered as an HTML <table>."
try: output = u'\n'.join(['<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()])
field = self.fields[name] return '<table>\n%s\n</table>' % output
except KeyError:
raise KeyError('Key %r not found in Form' % name)
return BoundField(self, field, name)
def _validate(self): def as_ul(self):
data_python = {} "Returns this form rendered as an HTML <ul>."
output = u'\n'.join(['<li>%s: %s</li>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()])
return '<ul>\n%s\n</ul>' % output
def as_table_with_errors(self):
"Returns this form rendered as an HTML <table>, with errors."
output = []
if self.errors().get(NON_FIELD_ERRORS):
# Errors not corresponding to a particular field are displayed at the top.
output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]]))
for name, field in self.fields.items():
bf = BoundField(self, field, name)
if bf.errors:
output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors]))
output.append('<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), bf))
return '<table>\n%s\n</table>' % '\n'.join(output)
def as_ul_with_errors(self):
"Returns this form rendered as an HTML <ul>, with errors."
output = []
if self.errors().get(NON_FIELD_ERRORS):
# Errors not corresponding to a particular field are displayed at the top.
output.append('<li><ul>%s</ul></li>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]]))
for name, field in self.fields.items():
bf = BoundField(self, field, name)
line = '<li>'
if bf.errors:
line += '<ul>%s</ul>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors])
line += '%s: %s</li>' % (pretty_name(name), bf)
output.append(line)
return '<ul>\n%s\n</ul>' % '\n'.join(output)
def full_clean(self):
"""
Cleans all of self.data and populates self.__errors and self.clean_data.
"""
self.clean_data = {}
errors = ErrorDict() errors = ErrorDict()
for name, field in self.fields.items(): for name, field in self.fields.items():
value = self.data.get(name, None)
try: try:
value = field.to_python(self.data.get(name, None)) value = field.clean(value)
data_python[name] = value self.clean_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.clean_data[name] = value
except ValidationError, e: except ValidationError, e:
errors[name] = e.messages errors[name] = e.messages
if not errors: # Only set self.data_python if there weren't errors. try:
self.__data_python = data_python self.clean_data = self.clean()
except ValidationError, e:
errors[NON_FIELD_ERRORS] = e.messages
if errors:
self.clean_data = None
self.__errors = errors self.__errors = errors
def clean(self):
"""
Hook for doing any extra form-wide cleaning after Field.clean() been
called on every field.
"""
return self.clean_data
class BoundField(object): class BoundField(object):
"A Field plus data" "A Field plus data"
def __init__(self, form, field, name): def __init__(self, form, field, name):

View File

@ -868,8 +868,11 @@ class Library(object):
dict = func(*args) dict = func(*args)
if not getattr(self, 'nodelist', False): if not getattr(self, 'nodelist', False):
from django.template.loader import get_template from django.template.loader import get_template, select_template
t = get_template(file_name) if hasattr(file_name, '__iter__'):
t = select_template(file_name)
else:
t = get_template(file_name)
self.nodelist = t.nodelist self.nodelist = t.nodelist
return self.nodelist.render(context_class(dict)) return self.nodelist.render(context_class(dict))

View File

@ -421,7 +421,11 @@ def filesizeformat(bytes):
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
bytes, etc). bytes, etc).
""" """
bytes = float(bytes) try:
bytes = float(bytes)
except TypeError:
return "0 bytes"
if bytes < 1024: if bytes < 1024:
return "%d byte%s" % (bytes, bytes != 1 and 's' or '') return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
if bytes < 1024 * 1024: if bytes < 1024 * 1024:

View File

@ -124,17 +124,27 @@ class ForNode(Node):
return nodelist.render(context) return nodelist.render(context)
class IfChangedNode(Node): class IfChangedNode(Node):
def __init__(self, nodelist): def __init__(self, nodelist, *varlist):
self.nodelist = nodelist self.nodelist = nodelist
self._last_seen = None self._last_seen = None
self._varlist = varlist
def render(self, context): def render(self, context):
if context.has_key('forloop') and context['forloop']['first']: if context.has_key('forloop') and context['forloop']['first']:
self._last_seen = None self._last_seen = None
content = self.nodelist.render(context) try:
if content != self._last_seen: if self._varlist:
# Consider multiple parameters.
# This automatically behaves like a OR evaluation of the multiple variables.
compare_to = [resolve_variable(var, context) for var in self._varlist]
else:
compare_to = self.nodelist.render(context)
except VariableDoesNotExist:
compare_to = None
if compare_to != self._last_seen:
firstloop = (self._last_seen == None) firstloop = (self._last_seen == None)
self._last_seen = content self._last_seen = compare_to
context.push() context.push()
context['ifchanged'] = {'firstloop': firstloop} context['ifchanged'] = {'firstloop': firstloop}
content = self.nodelist.render(context) content = self.nodelist.render(context)
@ -634,23 +644,34 @@ def ifchanged(parser, token):
""" """
Check if a value has changed from the last iteration of a loop. Check if a value has changed from the last iteration of a loop.
The 'ifchanged' block tag is used within a loop. It checks its own rendered The 'ifchanged' block tag is used within a loop. It has two possible uses.
contents against its previous state and only displays its content if the
value has changed::
<h1>Archive for {{ year }}</h1> 1. Checks its own rendered contents against its previous state and only
displays the content if it has changed. For example, this displays a list of
days, only displaying the month if it changes::
{% for date in days %} <h1>Archive for {{ year }}</h1>
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> {% for date in days %}
{% endfor %} {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
{% endfor %}
2. If given a variable, check whether that variable has changed. For example, the
following shows the date every time it changes, but only shows the hour if both
the hour and the date have changed::
{% for date in days %}
{% ifchanged date.date %} {{ date.date }} {% endifchanged %}
{% ifchanged date.hour date.date %}
{{ date.hour }}
{% endifchanged %}
{% endfor %}
""" """
bits = token.contents.split() bits = token.contents.split()
if len(bits) != 1:
raise TemplateSyntaxError, "'ifchanged' tag takes no arguments"
nodelist = parser.parse(('endifchanged',)) nodelist = parser.parse(('endifchanged',))
parser.delete_first_token() parser.delete_first_token()
return IfChangedNode(nodelist) return IfChangedNode(nodelist, *bits[1:])
ifchanged = register.tag(ifchanged) ifchanged = register.tag(ifchanged)
#@register.tag #@register.tag

View File

@ -610,6 +610,15 @@ fails. If no message is passed in, a default message is used.
string "123" is less than the string "2", for example. If you don't want string "123" is less than the string "2", for example. If you don't want
string comparison here, you will need to write your own validator. string comparison here, you will need to write your own validator.
``NumberIsInRange``
Takes two boundary numbers, ``lower`` and ``upper``, and checks that the
field is greater than ``lower`` (if given) and less than ``upper`` (if
given).
Both checks are inclusive. That is, ``NumberIsInRange(10, 20)`` will allow
values of both 10 and 20. This validator only checks numeric values
(e.g., float and integer values).
``IsAPowerOf`` ``IsAPowerOf``
Takes an integer argument and when called as a validator, checks that the Takes an integer argument and when called as a validator, checks that the
field being validated is a power of the integer. field being validated is a power of the integer.

View File

@ -265,6 +265,14 @@ Default: ``''`` (Empty string)
The name of the database to use. For SQLite, it's the full path to the database The name of the database to use. For SQLite, it's the full path to the database
file. file.
DATABASE_OPTIONS
----------------
Default: ``{}`` (Empty dictionary)
Extra parameters to use when connecting to the database. Consult backend
module's document for available keywords.
DATABASE_PASSWORD DATABASE_PASSWORD
----------------- -----------------
@ -814,6 +822,16 @@ manual configuration option (see below), Django will *not* touch the ``TZ``
environment variable, and it'll be up to you to ensure your processes are environment variable, and it'll be up to you to ensure your processes are
running in the correct environment. running in the correct environment.
URL_VALIDATOR_USER_AGENT
------------------------
Default: ``Django/<version> (http://www.djangoproject.com/)``
The string to use as the ``User-Agent`` header when checking to see if URLs
exist (see the ``verify_exists`` option on URLField_).
.. URLField: ../model_api/#urlfield
USE_ETAGS USE_ETAGS
--------- ---------

View File

@ -95,7 +95,7 @@ latest five news items::
from django.contrib.syndication.feeds import Feed from django.contrib.syndication.feeds import Feed
from chicagocrime.models import NewsItem from chicagocrime.models import NewsItem
class SiteNewsFeed(Feed): class LatestEntries(Feed):
title = "Chicagocrime.org site news" title = "Chicagocrime.org site news"
link = "/sitenews/" link = "/sitenews/"
description = "Updates on changes and additions to chicagocrime.org." description = "Updates on changes and additions to chicagocrime.org."
@ -120,14 +120,14 @@ One thing's left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
put into those elements. put into those elements.
* To specify the contents of ``<title>`` and ``<description>``, create * To specify the contents of ``<title>`` and ``<description>``, create
`Django templates`_ called ``feeds/sitenews_title.html`` and `Django templates`_ called ``feeds/latest_title.html`` and
``feeds/sitenews_description.html``, where ``sitenews`` is the ``slug`` ``feeds/latest_description.html``, where ``latest`` is the ``slug``
specified in the URLconf for the given feed. Note the ``.html`` extension specified in the URLconf for the given feed. Note the ``.html`` extension
is required. The RSS system renders that template for each item, passing is required. The RSS system renders that template for each item, passing
it two template context variables: it two template context variables:
* ``{{ obj }}`` -- The current object (one of whichever objects you * ``{{ obj }}`` -- The current object (one of whichever objects you
returned in ``items()``). returned in ``items()``).
* ``{{ site }}`` -- A ``django.models.core.sites.Site`` object * ``{{ site }}`` -- A ``django.models.core.sites.Site`` object
representing the current site. This is useful for representing the current site. This is useful for
``{{ site.domain }}`` or ``{{ site.name }}``. ``{{ site.domain }}`` or ``{{ site.name }}``.
@ -145,6 +145,16 @@ put into those elements.
Both ``get_absolute_url()`` and ``item_link()`` should return the item's Both ``get_absolute_url()`` and ``item_link()`` should return the item's
URL as a normal Python string. URL as a normal Python string.
* For the LatestEntries example above, we could have very simple feed templates:
* latest_title.html::
{{ obj.title }}
* latest_description.html::
{{ obj.description }}
.. _chicagocrime.org: http://www.chicagocrime.org/ .. _chicagocrime.org: http://www.chicagocrime.org/
.. _object-relational mapper: http://www.djangoproject.com/documentation/db_api/ .. _object-relational mapper: http://www.djangoproject.com/documentation/db_api/
.. _Django templates: http://www.djangoproject.com/documentation/templates/ .. _Django templates: http://www.djangoproject.com/documentation/templates/

View File

@ -473,7 +473,7 @@ block are output::
In the above, if ``athlete_list`` is not empty, the number of athletes will be In the above, if ``athlete_list`` is not empty, the number of athletes will be
displayed by the ``{{ athlete_list|length }}`` variable. displayed by the ``{{ athlete_list|length }}`` variable.
As you can see, the ``if`` tag can take an option ``{% else %}`` clause that As you can see, the ``if`` tag can take an optional ``{% else %}`` clause that
will be displayed if the test fails. will be displayed if the test fails.
``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or ``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
@ -525,16 +525,29 @@ ifchanged
Check if a value has changed from the last iteration of a loop. Check if a value has changed from the last iteration of a loop.
The ``ifchanged`` block tag is used within a loop. It checks its own rendered The 'ifchanged' block tag is used within a loop. It has two possible uses.
contents against its previous state and only displays its content if the value
has changed::
<h1>Archive for {{ year }}</h1> 1. Checks its own rendered contents against its previous state and only
displays the content if it has changed. For example, this displays a list of
days, only displaying the month if it changes::
{% for day in days %} <h1>Archive for {{ year }}</h1>
{% ifchanged %}<h3>{{ day|date:"F" }}</h3>{% endifchanged %}
<a href="{{ day|date:"M/d"|lower }}/">{{ day|date:"j" }}</a> {% for date in days %}
{% endfor %} {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
{% endfor %}
2. **New in Django development version.** If given a variable, check whether that
variable has changed. For example, the following shows the date every time it
changes, but only shows the hour if both the hour and the date has changed::
{% for date in days %}
{% ifchanged date.date %} {{ date.date }} {% endifchanged %}
{% ifchanged date.hour date.date %}
{{ date.hour }}
{% endifchanged %}
{% endfor %}
ifequal ifequal
~~~~~~~ ~~~~~~~
@ -558,7 +571,7 @@ The arguments can be hard-coded strings, so the following is valid::
It is only possible to compare an argument to template variables or strings. It is only possible to compare an argument to template variables or strings.
You cannot check for equality with Python objects such as ``True`` or You cannot check for equality with Python objects such as ``True`` or
``False``. If you need to test if something is true or false, use the ``if`` ``False``. If you need to test if something is true or false, use the ``if``
and ``ifnot`` tags instead. tag instead.
ifnotequal ifnotequal
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -321,7 +321,7 @@ Note::
def some_view(request): def some_view(request):
# ... # ...
return render_to_response('my_template'html', return render_to_response('my_template.html',
my_data_dictionary, my_data_dictionary,
context_instance=RequestContext(request)) context_instance=RequestContext(request))

View File

@ -10,7 +10,7 @@ used to validate that code behaves as expected. When refactoring or
modifying code, tests serve as a guide to ensure that behavior hasn't modifying code, tests serve as a guide to ensure that behavior hasn't
changed unexpectedly as a result of the refactor. changed unexpectedly as a result of the refactor.
Testing an web application is a complex task, as there are many Testing a web application is a complex task, as there are many
components of a web application that must be validated and tested. To components of a web application that must be validated and tested. To
help you test your application, Django provides a test execution help you test your application, Django provides a test execution
framework, and range of utilities that can be used to stimulate and framework, and range of utilities that can be used to stimulate and

View File

@ -64,4 +64,17 @@ True
>>> paginator.last_on_page(1) >>> paginator.last_on_page(1)
9 9
# Add a few more records to test out the orphans feature.
>>> for x in range(10, 13):
... Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save()
# With orphans set to 3 and 10 items per page, we should get all 12 items on a single page:
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3)
>>> paginator.pages
1
# With orphans only set to 1, we should get two pages:
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
>>> paginator.pages
2
"""} """}

View File

@ -343,63 +343,63 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
# CharField ################################################################### # CharField ###################################################################
>>> f = CharField(required=False) >>> f = CharField(required=False)
>>> f.to_python(1) >>> f.clean(1)
u'1' u'1'
>>> f.to_python('hello') >>> f.clean('hello')
u'hello' u'hello'
>>> f.to_python(None) >>> f.clean(None)
u'' u''
>>> f.to_python([1, 2, 3]) >>> f.clean([1, 2, 3])
u'[1, 2, 3]' u'[1, 2, 3]'
CharField accepts an optional max_length parameter: CharField accepts an optional max_length parameter:
>>> f = CharField(max_length=10, required=False) >>> f = CharField(max_length=10, required=False)
>>> f.to_python('') >>> f.clean('')
u'' u''
>>> f.to_python('12345') >>> f.clean('12345')
u'12345' u'12345'
>>> f.to_python('1234567890') >>> f.clean('1234567890')
u'1234567890' u'1234567890'
>>> f.to_python('1234567890a') >>> f.clean('1234567890a')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Ensure this value has at most 10 characters.'] ValidationError: [u'Ensure this value has at most 10 characters.']
CharField accepts an optional min_length parameter: CharField accepts an optional min_length parameter:
>>> f = CharField(min_length=10, required=False) >>> f = CharField(min_length=10, required=False)
>>> f.to_python('') >>> f.clean('')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Ensure this value has at least 10 characters.'] ValidationError: [u'Ensure this value has at least 10 characters.']
>>> f.to_python('12345') >>> f.clean('12345')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Ensure this value has at least 10 characters.'] ValidationError: [u'Ensure this value has at least 10 characters.']
>>> f.to_python('1234567890') >>> f.clean('1234567890')
u'1234567890' u'1234567890'
>>> f.to_python('1234567890a') >>> f.clean('1234567890a')
u'1234567890a' u'1234567890a'
# IntegerField ################################################################ # IntegerField ################################################################
>>> f = IntegerField() >>> f = IntegerField()
>>> f.to_python('1') >>> f.clean('1')
1 1
>>> isinstance(f.to_python('1'), int) >>> isinstance(f.clean('1'), int)
True True
>>> f.to_python('23') >>> f.clean('23')
23 23
>>> f.to_python('a') >>> f.clean('a')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a whole number.'] ValidationError: [u'Enter a whole number.']
>>> f.to_python('1 ') >>> f.clean('1 ')
1 1
>>> f.to_python(' 1') >>> f.clean(' 1')
1 1
>>> f.to_python(' 1 ') >>> f.clean(' 1 ')
1 1
>>> f.to_python('1a') >>> f.clean('1a')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a whole number.'] ValidationError: [u'Enter a whole number.']
@ -408,75 +408,75 @@ ValidationError: [u'Enter a whole number.']
>>> import datetime >>> import datetime
>>> f = DateField() >>> f = DateField()
>>> f.to_python(datetime.date(2006, 10, 25)) >>> f.clean(datetime.date(2006, 10, 25))
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('2006-10-25') >>> f.clean('2006-10-25')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('10/25/2006') >>> f.clean('10/25/2006')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('10/25/06') >>> f.clean('10/25/06')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('Oct 25 2006') >>> f.clean('Oct 25 2006')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('October 25 2006') >>> f.clean('October 25 2006')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('October 25, 2006') >>> f.clean('October 25, 2006')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('25 October 2006') >>> f.clean('25 October 2006')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('25 October, 2006') >>> f.clean('25 October, 2006')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('2006-4-31') >>> f.clean('2006-4-31')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date.'] ValidationError: [u'Enter a valid date.']
>>> f.to_python('200a-10-25') >>> f.clean('200a-10-25')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date.'] ValidationError: [u'Enter a valid date.']
>>> f.to_python('25/10/06') >>> f.clean('25/10/06')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date.'] ValidationError: [u'Enter a valid date.']
>>> f.to_python(None) >>> f.clean(None)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This field is required.'] ValidationError: [u'This field is required.']
>>> f = DateField(required=False) >>> f = DateField(required=False)
>>> f.to_python(None) >>> f.clean(None)
>>> repr(f.to_python(None)) >>> repr(f.clean(None))
'None' 'None'
>>> f.to_python('') >>> f.clean('')
>>> repr(f.to_python('')) >>> repr(f.clean(''))
'None' 'None'
DateField accepts an optional input_formats parameter: DateField accepts an optional input_formats parameter:
>>> f = DateField(input_formats=['%Y %m %d']) >>> f = DateField(input_formats=['%Y %m %d'])
>>> f.to_python(datetime.date(2006, 10, 25)) >>> f.clean(datetime.date(2006, 10, 25))
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
>>> f.to_python('2006 10 25') >>> f.clean('2006 10 25')
datetime.date(2006, 10, 25) datetime.date(2006, 10, 25)
The input_formats parameter overrides all default input formats, The input_formats parameter overrides all default input formats,
so the default formats won't work unless you specify them: so the default formats won't work unless you specify them:
>>> f.to_python('2006-10-25') >>> f.clean('2006-10-25')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date.'] ValidationError: [u'Enter a valid date.']
>>> f.to_python('10/25/2006') >>> f.clean('10/25/2006')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date.'] ValidationError: [u'Enter a valid date.']
>>> f.to_python('10/25/06') >>> f.clean('10/25/06')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date.'] ValidationError: [u'Enter a valid date.']
@ -485,63 +485,63 @@ ValidationError: [u'Enter a valid date.']
>>> import datetime >>> import datetime
>>> f = DateTimeField() >>> f = DateTimeField()
>>> f.to_python(datetime.date(2006, 10, 25)) >>> f.clean(datetime.date(2006, 10, 25))
datetime.datetime(2006, 10, 25, 0, 0) datetime.datetime(2006, 10, 25, 0, 0)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
datetime.datetime(2006, 10, 25, 14, 30, 59) datetime.datetime(2006, 10, 25, 14, 30, 59)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
datetime.datetime(2006, 10, 25, 14, 30, 59, 200) datetime.datetime(2006, 10, 25, 14, 30, 59, 200)
>>> f.to_python('2006-10-25 14:30:45') >>> f.clean('2006-10-25 14:30:45')
datetime.datetime(2006, 10, 25, 14, 30, 45) datetime.datetime(2006, 10, 25, 14, 30, 45)
>>> f.to_python('2006-10-25 14:30:00') >>> f.clean('2006-10-25 14:30:00')
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python('2006-10-25 14:30') >>> f.clean('2006-10-25 14:30')
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python('2006-10-25') >>> f.clean('2006-10-25')
datetime.datetime(2006, 10, 25, 0, 0) datetime.datetime(2006, 10, 25, 0, 0)
>>> f.to_python('10/25/2006 14:30:45') >>> f.clean('10/25/2006 14:30:45')
datetime.datetime(2006, 10, 25, 14, 30, 45) datetime.datetime(2006, 10, 25, 14, 30, 45)
>>> f.to_python('10/25/2006 14:30:00') >>> f.clean('10/25/2006 14:30:00')
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python('10/25/2006 14:30') >>> f.clean('10/25/2006 14:30')
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python('10/25/2006') >>> f.clean('10/25/2006')
datetime.datetime(2006, 10, 25, 0, 0) datetime.datetime(2006, 10, 25, 0, 0)
>>> f.to_python('10/25/06 14:30:45') >>> f.clean('10/25/06 14:30:45')
datetime.datetime(2006, 10, 25, 14, 30, 45) datetime.datetime(2006, 10, 25, 14, 30, 45)
>>> f.to_python('10/25/06 14:30:00') >>> f.clean('10/25/06 14:30:00')
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python('10/25/06 14:30') >>> f.clean('10/25/06 14:30')
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python('10/25/06') >>> f.clean('10/25/06')
datetime.datetime(2006, 10, 25, 0, 0) datetime.datetime(2006, 10, 25, 0, 0)
>>> f.to_python('hello') >>> f.clean('hello')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date/time.'] ValidationError: [u'Enter a valid date/time.']
>>> f.to_python('2006-10-25 4:30 p.m.') >>> f.clean('2006-10-25 4:30 p.m.')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date/time.'] ValidationError: [u'Enter a valid date/time.']
DateField accepts an optional input_formats parameter: DateField accepts an optional input_formats parameter:
>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p']) >>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
>>> f.to_python(datetime.date(2006, 10, 25)) >>> f.clean(datetime.date(2006, 10, 25))
datetime.datetime(2006, 10, 25, 0, 0) datetime.datetime(2006, 10, 25, 0, 0)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
datetime.datetime(2006, 10, 25, 14, 30, 59) datetime.datetime(2006, 10, 25, 14, 30, 59)
>>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) >>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
datetime.datetime(2006, 10, 25, 14, 30, 59, 200) datetime.datetime(2006, 10, 25, 14, 30, 59, 200)
>>> f.to_python('2006 10 25 2:30 PM') >>> f.clean('2006 10 25 2:30 PM')
datetime.datetime(2006, 10, 25, 14, 30) datetime.datetime(2006, 10, 25, 14, 30)
The input_formats parameter overrides all default input formats, The input_formats parameter overrides all default input formats,
so the default formats won't work unless you specify them: so the default formats won't work unless you specify them:
>>> f.to_python('2006-10-25 14:30:45') >>> f.clean('2006-10-25 14:30:45')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid date/time.'] ValidationError: [u'Enter a valid date/time.']
@ -549,51 +549,51 @@ ValidationError: [u'Enter a valid date/time.']
# RegexField ################################################################## # RegexField ##################################################################
>>> f = RegexField('^\d[A-F]\d$') >>> f = RegexField('^\d[A-F]\d$')
>>> f.to_python('2A2') >>> f.clean('2A2')
u'2A2' u'2A2'
>>> f.to_python('3F3') >>> f.clean('3F3')
u'3F3' u'3F3'
>>> f.to_python('3G3') >>> f.clean('3G3')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid value.'] ValidationError: [u'Enter a valid value.']
>>> f.to_python(' 2A2') >>> f.clean(' 2A2')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid value.'] ValidationError: [u'Enter a valid value.']
>>> f.to_python('2A2 ') >>> f.clean('2A2 ')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid value.'] ValidationError: [u'Enter a valid value.']
Alternatively, RegexField can take a compiled regular expression: Alternatively, RegexField can take a compiled regular expression:
>>> f = RegexField(re.compile('^\d[A-F]\d$')) >>> f = RegexField(re.compile('^\d[A-F]\d$'))
>>> f.to_python('2A2') >>> f.clean('2A2')
u'2A2' u'2A2'
>>> f.to_python('3F3') >>> f.clean('3F3')
u'3F3' u'3F3'
>>> f.to_python('3G3') >>> f.clean('3G3')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid value.'] ValidationError: [u'Enter a valid value.']
>>> f.to_python(' 2A2') >>> f.clean(' 2A2')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid value.'] ValidationError: [u'Enter a valid value.']
>>> f.to_python('2A2 ') >>> f.clean('2A2 ')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid value.'] ValidationError: [u'Enter a valid value.']
RegexField takes an optional error_message argument: RegexField takes an optional error_message argument:
>>> f = RegexField('^\d\d\d\d$', 'Enter a four-digit number.') >>> f = RegexField('^\d\d\d\d$', 'Enter a four-digit number.')
>>> f.to_python('1234') >>> f.clean('1234')
u'1234' u'1234'
>>> f.to_python('123') >>> f.clean('123')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a four-digit number.'] ValidationError: [u'Enter a four-digit number.']
>>> f.to_python('abcd') >>> f.clean('abcd')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a four-digit number.'] ValidationError: [u'Enter a four-digit number.']
@ -601,17 +601,17 @@ ValidationError: [u'Enter a four-digit number.']
# EmailField ################################################################## # EmailField ##################################################################
>>> f = EmailField() >>> f = EmailField()
>>> f.to_python('person@example.com') >>> f.clean('person@example.com')
u'person@example.com' u'person@example.com'
>>> f.to_python('foo') >>> f.clean('foo')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid e-mail address.'] ValidationError: [u'Enter a valid e-mail address.']
>>> f.to_python('foo@') >>> f.clean('foo@')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid e-mail address.'] ValidationError: [u'Enter a valid e-mail address.']
>>> f.to_python('foo@bar') >>> f.clean('foo@bar')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid e-mail address.'] ValidationError: [u'Enter a valid e-mail address.']
@ -619,31 +619,31 @@ ValidationError: [u'Enter a valid e-mail address.']
# URLField ################################################################## # URLField ##################################################################
>>> f = URLField() >>> f = URLField()
>>> f.to_python('http://example.com') >>> f.clean('http://example.com')
u'http://example.com' u'http://example.com'
>>> f.to_python('http://www.example.com') >>> f.clean('http://www.example.com')
u'http://www.example.com' u'http://www.example.com'
>>> f.to_python('foo') >>> f.clean('foo')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid URL.'] ValidationError: [u'Enter a valid URL.']
>>> f.to_python('example.com') >>> f.clean('example.com')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid URL.'] ValidationError: [u'Enter a valid URL.']
>>> f.to_python('http://') >>> f.clean('http://')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid URL.'] ValidationError: [u'Enter a valid URL.']
>>> f.to_python('http://example') >>> f.clean('http://example')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid URL.'] ValidationError: [u'Enter a valid URL.']
>>> f.to_python('http://example.') >>> f.clean('http://example.')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid URL.'] ValidationError: [u'Enter a valid URL.']
>>> f.to_python('http://.com') >>> f.clean('http://.com')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid URL.'] ValidationError: [u'Enter a valid URL.']
@ -651,17 +651,17 @@ ValidationError: [u'Enter a valid URL.']
URLField takes an optional verify_exists parameter, which is False by default. URLField takes an optional verify_exists parameter, which is False by default.
This verifies that the URL is live on the Internet and doesn't return a 404 or 500: This verifies that the URL is live on the Internet and doesn't return a 404 or 500:
>>> f = URLField(verify_exists=True) >>> f = URLField(verify_exists=True)
>>> f.to_python('http://www.google.com') >>> f.clean('http://www.google.com') # This will fail if there's no Internet connection
u'http://www.google.com' u'http://www.google.com'
>>> f.to_python('http://example') >>> f.clean('http://example')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid URL.'] ValidationError: [u'Enter a valid URL.']
>>> f.to_python('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com') # bad domain >>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com') # bad domain
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This URL appears to be a broken link.'] ValidationError: [u'This URL appears to be a broken link.']
>>> f.to_python('http://google.com/we-love-microsoft.html') # good domain, bad page >>> f.clean('http://google.com/we-love-microsoft.html') # good domain, bad page
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This URL appears to be a broken link.'] ValidationError: [u'This URL appears to be a broken link.']
@ -669,41 +669,41 @@ ValidationError: [u'This URL appears to be a broken link.']
# BooleanField ################################################################ # BooleanField ################################################################
>>> f = BooleanField() >>> f = BooleanField()
>>> f.to_python(True) >>> f.clean(True)
True True
>>> f.to_python(False) >>> f.clean(False)
False False
>>> f.to_python(1) >>> f.clean(1)
True True
>>> f.to_python(0) >>> f.clean(0)
False False
>>> f.to_python('Django rocks') >>> f.clean('Django rocks')
True True
# ChoiceField ################################################################# # ChoiceField #################################################################
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')]) >>> f = ChoiceField(choices=[('1', '1'), ('2', '2')])
>>> f.to_python(1) >>> f.clean(1)
u'1' u'1'
>>> f.to_python('1') >>> f.clean('1')
u'1' u'1'
>>> f.to_python(None) >>> f.clean(None)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This field is required.'] ValidationError: [u'This field is required.']
>>> f.to_python('') >>> f.clean('')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This field is required.'] ValidationError: [u'This field is required.']
>>> f.to_python('3') >>> f.clean('3')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')]) >>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
>>> f.to_python('J') >>> f.clean('J')
u'J' u'J'
>>> f.to_python('John') >>> f.clean('John')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Select a valid choice. John is not one of the available choices.'] ValidationError: [u'Select a valid choice. John is not one of the available choices.']
@ -711,39 +711,98 @@ ValidationError: [u'Select a valid choice. John is not one of the available choi
# MultipleChoiceField ######################################################### # MultipleChoiceField #########################################################
>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')]) >>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')])
>>> f.to_python([1]) >>> f.clean([1])
[u'1'] [u'1']
>>> f.to_python(['1']) >>> f.clean(['1'])
[u'1'] [u'1']
>>> f.to_python(['1', '2']) >>> f.clean(['1', '2'])
[u'1', u'2'] [u'1', u'2']
>>> f.to_python([1, '2']) >>> f.clean([1, '2'])
[u'1', u'2'] [u'1', u'2']
>>> f.to_python((1, '2')) >>> f.clean((1, '2'))
[u'1', u'2'] [u'1', u'2']
>>> f.to_python('hello') >>> f.clean('hello')
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Enter a list of values.'] ValidationError: [u'Enter a list of values.']
>>> f.to_python([]) >>> f.clean([])
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This field is required.'] ValidationError: [u'This field is required.']
>>> f.to_python(()) >>> f.clean(())
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This field is required.'] ValidationError: [u'This field is required.']
>>> f.to_python(['3']) >>> f.clean(['3'])
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
# ComboField ##################################################################
ComboField takes a list of fields that should be used to validate a value,
in that order:
>>> f = ComboField(fields=[CharField(max_length=20), EmailField()])
>>> f.clean('test@example.com')
u'test@example.com'
>>> f.clean('longemailaddress@example.com')
Traceback (most recent call last):
...
ValidationError: [u'Ensure this value has at most 20 characters.']
>>> f.clean('not an e-mail')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid e-mail address.']
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
# Form ######################################################################## # Form ########################################################################
>>> class Person(Form): >>> class Person(Form):
... first_name = CharField() ... first_name = CharField()
... last_name = CharField() ... last_name = CharField()
... birthday = DateField() ... birthday = DateField()
>>> p = Person()
>>> print p
<table>
<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
</table>
>>> print p.as_table()
<table>
<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
</table>
>>> print p.as_ul()
<ul>
<li>First name: <input type="text" name="first_name" /></li>
<li>Last name: <input type="text" name="last_name" /></li>
<li>Birthday: <input type="text" name="birthday" /></li>
</ul>
>>> print p.as_table_with_errors()
<table>
<tr><td colspan="2"><ul><li>This field is required.</li></ul></td></tr>
<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
<tr><td colspan="2"><ul><li>This field is required.</li></ul></td></tr>
<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
<tr><td colspan="2"><ul><li>This field is required.</li></ul></td></tr>
<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
</table>
>>> print p.as_ul_with_errors()
<ul>
<li><ul><li>This field is required.</li></ul>First name: <input type="text" name="first_name" /></li>
<li><ul><li>This field is required.</li></ul>Last name: <input type="text" name="last_name" /></li>
<li><ul><li>This field is required.</li></ul>Birthday: <input type="text" name="birthday" /></li>
</ul>
>>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'}) >>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'})
>>> p.errors() >>> p.errors()
{} {}
@ -753,7 +812,7 @@ True
u'' u''
>>> p.errors().as_text() >>> p.errors().as_text()
u'' u''
>>> p.to_python() >>> p.clean()
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
>>> print p['first_name'] >>> print p['first_name']
<input type="text" name="first_name" value="John" /> <input type="text" name="first_name" value="John" />
@ -766,6 +825,12 @@ u''
<input type="text" name="first_name" value="John" /> <input type="text" name="first_name" value="John" />
<input type="text" name="last_name" value="Lennon" /> <input type="text" name="last_name" value="Lennon" />
<input type="text" name="birthday" value="1940-10-9" /> <input type="text" name="birthday" value="1940-10-9" />
>>> print p
<table>
<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>
<tr><td>Last name:</td><td><input type="text" name="last_name" value="Lennon" /></td></tr>
<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
</table>
>>> p = Person({'last_name': u'Lennon'}) >>> p = Person({'last_name': u'Lennon'})
>>> p.errors() >>> p.errors()
@ -779,8 +844,8 @@ u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is re
* This field is required. * This field is required.
* birthday * birthday
* This field is required. * This field is required.
>>> p.to_python() >>> p.clean()
>>> repr(p.to_python()) >>> repr(p.clean())
'None' 'None'
>>> p['first_name'].errors >>> p['first_name'].errors
[u'This field is required.'] [u'This field is required.']
@ -887,6 +952,84 @@ MultipleChoiceField is a special case, as its data is required to be a list:
<option value="J">John Lennon</option> <option value="J">John Lennon</option>
<option value="P" selected="selected">Paul McCartney</option> <option value="P" selected="selected">Paul McCartney</option>
</select> </select>
There are a couple of ways to do multiple-field validation. If you want the
validation message to be associated with a particular field, implement the
clean_XXX() method on the Form, where XXX is the field name. As in
Field.clean(), the clean_XXX() method should return the cleaned value:
>>> class UserRegistration(Form):
... username = CharField(max_length=10)
... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput)
... def clean_password2(self):
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data['password2']
>>> f = UserRegistration()
>>> f.errors()
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
>>> f.errors()
{'password2': [u'Please make sure your passwords match.']}
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
>>> f.errors()
{}
>>> f.clean()
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
Another way of doing multiple-field validation is by implementing the
Form's clean() method. If you do this, any ValidationError raised by that
method will not be associated with a particular field; it will have a
special-case association with the field named '__all__'. Note that
Form.clean() still needs to return a dictionary of all clean data:
>>> class UserRegistration(Form):
... username = CharField(max_length=10)
... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput)
... def clean(self):
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data
>>> f = UserRegistration()
>>> print f.as_table()
<table>
<tr><td>Username:</td><td><input type="text" name="username" /></td></tr>
<tr><td>Password1:</td><td><input type="password" name="password1" /></td></tr>
<tr><td>Password2:</td><td><input type="password" name="password2" /></td></tr>
</table>
>>> f.errors()
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
>>> f.errors()
{'__all__': [u'Please make sure your passwords match.']}
>>> print f.as_table()
<table>
<tr><td>Username:</td><td><input type="text" name="username" value="adrian" /></td></tr>
<tr><td>Password1:</td><td><input type="password" name="password1" value="foo" /></td></tr>
<tr><td>Password2:</td><td><input type="password" name="password2" value="bar" /></td></tr>
</table>
>>> print f.as_table_with_errors()
<table>
<tr><td colspan="2"><ul><li>Please make sure your passwords match.</li></ul></td></tr>
<tr><td>Username:</td><td><input type="text" name="username" value="adrian" /></td></tr>
<tr><td>Password1:</td><td><input type="password" name="password1" value="foo" /></td></tr>
<tr><td>Password2:</td><td><input type="password" name="password2" value="bar" /></td></tr>
</table>
>>> print f.as_ul_with_errors()
<ul>
<li><ul><li>Please make sure your passwords match.</li></ul></li>
<li>Username: <input type="text" name="username" value="adrian" /></li>
<li>Password1: <input type="password" name="password1" value="foo" /></li>
<li>Password2: <input type="password" name="password2" value="bar" /></li>
</ul>
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
>>> f.errors()
{}
>>> f.clean()
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
""" """
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -328,6 +328,21 @@ class Templates(unittest.TestCase):
'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'), 'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'), 'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'), 'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
# Test one parameter given to ifchanged.
'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'),
'ifchanged-param02': ('{% for n in num %}{% for x in numx %}{% ifchanged n %}..{% endifchanged %}{{ x }}{% endfor %}{% endfor %}', { 'num': (1,2,3), 'numx': (5,6,7) }, '..567..567..567'),
# Test multiple parameters to ifchanged.
'ifchanged-param03': ('{% for n in num %}{{ n }}{% for x in numx %}{% ifchanged x n %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1,1,2), 'numx': (5,6,6) }, '156156256'),
# Test a date+hour like construct, where the hour of the last day
# is the same but the date had changed, so print the hour anyway.
'ifchanged-param04': ('{% for d in days %}{% ifchanged %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
# Logically the same as above, just written with explicit
# ifchanged for the day.
'ifchanged-param04': ('{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
### IFEQUAL TAG ########################################################### ### IFEQUAL TAG ###########################################################
'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""), 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),