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

newforms-admin: Merged to [6652]

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6656 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2007-11-06 22:25:59 +00:00
parent 6f15904669
commit e29358827d
46 changed files with 1768 additions and 467 deletions

View File

@ -1016,7 +1016,7 @@ msgstr "Escoja %s para modificar"
#: contrib/admin/views/main.py:780 #: contrib/admin/views/main.py:780
msgid "Database error" msgid "Database error"
msgstr "Erorr en la base de datos" msgstr "Error en la base de datos"
#: contrib/auth/forms.py:17 #: contrib/auth/forms.py:17
#: contrib/auth/forms.py:138 #: contrib/auth/forms.py:138

View File

@ -104,7 +104,7 @@ class PasswordResetForm(oldforms.Manipulator):
'site_name': site_name, 'site_name': site_name,
'user': user, 'user': user,
} }
send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [user.email]) send_mail(_('Password reset on %s') % site_name, t.render(Context(c)), None, [user.email])
class PasswordChangeForm(oldforms.Manipulator): class PasswordChangeForm(oldforms.Manipulator):
"A form that lets a user change his password." "A form that lets a user change his password."

View File

@ -1,8 +1,8 @@
import time
from django.conf import settings from django.conf import settings
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
from email.Utils import formatdate from django.utils.http import cookie_date
import datetime
import time
TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_NAME = 'testcookie'
TEST_COOKIE_VALUE = 'worked' TEST_COOKIE_VALUE = 'worked'
@ -10,8 +10,9 @@ TEST_COOKIE_VALUE = 'worked'
class SessionMiddleware(object): class SessionMiddleware(object):
def process_request(self, request): def process_request(self, request):
engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)) session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = engine.SessionStore(session_key)
def process_response(self, request, response): def process_response(self, request, response):
# If request.session was modified, or if response.session was set, save # If request.session was modified, or if response.session was set, save
@ -30,13 +31,8 @@ class SessionMiddleware(object):
expires = None expires = None
else: else:
max_age = settings.SESSION_COOKIE_AGE max_age = settings.SESSION_COOKIE_AGE
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE) expires_time = time.time() + settings.SESSION_COOKIE_AGE
expires = cookie_date(expires_time)
# Fixed length date must have '-' separation in the format
# DD-MMM-YYYY for compliance with Netscape cookie standard
expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
# Save the seesion data and refresh the client cookie. # Save the seesion data and refresh the client cookie.
request.session.save() request.session.save()
response.set_cookie(settings.SESSION_COOKIE_NAME, response.set_cookie(settings.SESSION_COOKIE_NAME,

View File

@ -242,6 +242,8 @@ def setup_environ(settings_mod):
""" """
Configures the runtime environment. This can also be used by external Configures the runtime environment. This can also be used by external
scripts wanting to set up a similar environment to manage.py. scripts wanting to set up a similar environment to manage.py.
Returns the project directory (assuming the passed settings module is
directly in the project directory).
""" """
# Add this project to sys.path so that it's importable in the conventional # Add this project to sys.path so that it's importable in the conventional
# way. For example, if this file (manage.py) lives in a directory # way. For example, if this file (manage.py) lives in a directory

View File

@ -1,8 +1,10 @@
from django.core.management.base import copy_helper, CommandError, LabelCommand
import os import os
from django.core.management.base import copy_helper, CommandError, LabelCommand
class Command(LabelCommand): class Command(LabelCommand):
help = "Creates a Django app directory structure for the given app name in the current directory." help = ("Creates a Django app directory structure for the given app name"
" in the current directory.")
args = "[appname]" args = "[appname]"
label = 'application name' label = 'application name'
@ -14,17 +16,18 @@ class Command(LabelCommand):
def handle_label(self, app_name, directory=None, **options): def handle_label(self, app_name, directory=None, **options):
if directory is None: if directory is None:
directory = os.getcwd() directory = os.getcwd()
# Determine the project_name a bit naively -- by looking at the name of # Determine the project_name by using the basename of directory,
# the parent directory. # which should be the full path of the project directory (or the
project_dir = os.path.normpath(os.path.join(directory, os.pardir)) # current directory if no directory was passed).
parent_dir = os.path.basename(project_dir)
project_name = os.path.basename(directory) project_name = os.path.basename(directory)
if app_name == project_name: if app_name == project_name:
raise CommandError("You cannot create an app with the same name (%r) as your project." % app_name) raise CommandError("You cannot create an app with the same name"
copy_helper(self.style, 'app', app_name, directory, parent_dir) " (%r) as your project." % app_name)
copy_helper(self.style, 'app', app_name, directory, project_name)
class ProjectCommand(Command): class ProjectCommand(Command):
help = "Creates a Django app directory structure for the given app name in this project's directory." help = ("Creates a Django app directory structure for the given app name"
" in this project's directory.")
def __init__(self, project_directory): def __init__(self, project_directory):
super(ProjectCommand, self).__init__() super(ProjectCommand, self).__init__()

View File

@ -252,6 +252,7 @@ def sql_model_create(model, style, known_models=set()):
table_output = [] table_output = []
pending_references = {} pending_references = {}
qn = connection.ops.quote_name qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
for f in opts.fields: for f in opts.fields:
col_type = f.db_type() col_type = f.db_type()
tablespace = f.db_tablespace or opts.db_tablespace tablespace = f.db_tablespace or opts.db_tablespace
@ -272,7 +273,7 @@ def sql_model_create(model, style, known_models=set()):
# won't be generating a CREATE INDEX statement for this field. # won't be generating a CREATE INDEX statement for this field.
field_output.append(connection.ops.tablespace_sql(tablespace, inline=True)) field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
if f.rel: if f.rel:
if f.rel.to in known_models: if inline_references and f.rel.to in known_models:
field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \ style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \
style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' + style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' +
@ -341,10 +342,12 @@ def sql_for_pending_references(model, style, pending_references):
def many_to_many_sql_for_model(model, style): def many_to_many_sql_for_model(model, style):
from django.db import connection, models from django.db import connection, models
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.db.backends.util import truncate_name
opts = model._meta opts = model._meta
final_output = [] final_output = []
qn = connection.ops.quote_name qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
for f in opts.many_to_many: for f in opts.many_to_many:
if not isinstance(f.rel, generic.GenericRel): if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace tablespace = f.db_tablespace or opts.db_tablespace
@ -354,26 +357,43 @@ def many_to_many_sql_for_model(model, style):
tablespace_sql = '' tablespace_sql = ''
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
style.SQL_TABLE(qn(f.m2m_db_table())) + ' ('] style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
table_output.append(' %s %s %s%s,' % \ table_output.append(' %s %s %s%s,' %
(style.SQL_FIELD(qn('id')), (style.SQL_FIELD(qn('id')),
style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()), style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
style.SQL_KEYWORD('NOT NULL PRIMARY KEY'), style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
tablespace_sql)) tablespace_sql))
table_output.append(' %s %s %s %s (%s)%s,' % \ if inline_references:
(style.SQL_FIELD(qn(f.m2m_column_name())), deferred = []
style.SQL_COLTYPE(models.ForeignKey(model).db_type()), table_output.append(' %s %s %s %s (%s)%s,' %
style.SQL_KEYWORD('NOT NULL REFERENCES'), (style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_TABLE(qn(opts.db_table)), style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
style.SQL_FIELD(qn(opts.pk.column)), style.SQL_KEYWORD('NOT NULL REFERENCES'),
connection.ops.deferrable_sql())) style.SQL_TABLE(qn(opts.db_table)),
table_output.append(' %s %s %s %s (%s)%s,' % \ style.SQL_FIELD(qn(opts.pk.column)),
(style.SQL_FIELD(qn(f.m2m_reverse_name())), connection.ops.deferrable_sql()))
style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()), table_output.append(' %s %s %s %s (%s)%s,' %
style.SQL_KEYWORD('NOT NULL REFERENCES'), (style.SQL_FIELD(qn(f.m2m_reverse_name())),
style.SQL_TABLE(qn(f.rel.to._meta.db_table)), style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
style.SQL_FIELD(qn(f.rel.to._meta.pk.column)), style.SQL_KEYWORD('NOT NULL REFERENCES'),
connection.ops.deferrable_sql())) style.SQL_TABLE(qn(f.rel.to._meta.db_table)),
table_output.append(' %s (%s, %s)%s' % \ style.SQL_FIELD(qn(f.rel.to._meta.pk.column)),
connection.ops.deferrable_sql()))
else:
table_output.append(' %s %s %s,' %
(style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
style.SQL_KEYWORD('NOT NULL')))
table_output.append(' %s %s %s,' %
(style.SQL_FIELD(qn(f.m2m_reverse_name())),
style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
style.SQL_KEYWORD('NOT NULL')))
deferred = [
(f.m2m_db_table(), f.m2m_column_name(), opts.db_table,
opts.pk.column),
( f.m2m_db_table(), f.m2m_reverse_name(),
f.rel.to._meta.db_table, f.rel.to._meta.pk.column)
]
table_output.append(' %s (%s, %s)%s' %
(style.SQL_KEYWORD('UNIQUE'), (style.SQL_KEYWORD('UNIQUE'),
style.SQL_FIELD(qn(f.m2m_column_name())), style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_FIELD(qn(f.m2m_reverse_name())), style.SQL_FIELD(qn(f.m2m_reverse_name())),
@ -385,6 +405,15 @@ def many_to_many_sql_for_model(model, style):
table_output.append(';') table_output.append(';')
final_output.append('\n'.join(table_output)) final_output.append('\n'.join(table_output))
for r_table, r_col, table, col in deferred:
r_name = '%s_refs_%s_%x' % (r_col, col,
abs(hash((r_table, table))))
final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
(qn(r_table),
truncate_name(r_name, connection.ops.max_name_length()),
qn(r_col), qn(table), qn(col),
connection.ops.deferrable_sql()))
# Add any extra SQL needed to support auto-incrementing PKs # Add any extra SQL needed to support auto-incrementing PKs
autoinc_sql = connection.ops.autoinc_sql(f.m2m_db_table(), 'id') autoinc_sql = connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
if autoinc_sql: if autoinc_sql:

View File

@ -9,14 +9,14 @@ been reviewed for security issues. Don't use it for production use.
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from types import ListType, StringType from types import ListType, StringType
from email.Utils import formatdate
import mimetypes import mimetypes
import os import os
import re import re
import sys import sys
import time
import urllib import urllib
from django.utils.http import http_date
__version__ = "0.1" __version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler','demo_app'] __all__ = ['WSGIServer','WSGIRequestHandler','demo_app']
@ -376,7 +376,7 @@ class ServerHandler(object):
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status)) self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
if 'Date' not in self.headers: if 'Date' not in self.headers:
self._write( self._write(
'Date: %s\r\n' % (formatdate()[:26] + "GMT") 'Date: %s\r\n' % http_date()
) )
if self.server_software and 'Server' not in self.headers: if self.server_software and 'Server' not in self.headers:
self._write('Server: %s\r\n' % self.server_software) self._write('Server: %s\r\n' % self.server_software)

View File

@ -43,6 +43,7 @@ class BaseDatabaseFeatures(object):
allows_group_by_ordinal = True allows_group_by_ordinal = True
allows_unique_and_pk = True allows_unique_and_pk = True
autoindexes_primary_keys = True autoindexes_primary_keys = True
inline_fk_references = True
needs_datetime_string_cast = True needs_datetime_string_cast = True
needs_upper_for_iops = False needs_upper_for_iops = False
supports_constraints = True supports_constraints = True

View File

@ -61,6 +61,7 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False autoindexes_primary_keys = False
inline_fk_references = False
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name): def date_extract_sql(self, lookup_type, field_name):

View File

@ -65,6 +65,7 @@ class MysqlDebugWrapper:
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False autoindexes_primary_keys = False
inline_fk_references = False
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name): def date_extract_sql(self, lookup_type, field_name):

View File

@ -7,6 +7,7 @@ from django.db.models.query import Q
from django.db.models.manager import Manager from django.db.models.manager import Manager
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.fields import * from django.db.models.fields import *
from django.db.models.fields.subclassing import SubfieldBase
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
from django.db.models import signals from django.db.models import signals
from django.utils.functional import curry from django.utils.functional import curry

View File

@ -145,6 +145,8 @@ class Field(object):
# exactly which wacky database column type you want to use. # exactly which wacky database column type you want to use.
data_types = get_creation_module().DATA_TYPES data_types = get_creation_module().DATA_TYPES
internal_type = self.get_internal_type() internal_type = self.get_internal_type()
if internal_type not in data_types:
return None
return data_types[internal_type] % self.__dict__ return data_types[internal_type] % self.__dict__
def validate_full(self, field_data, all_data): def validate_full(self, field_data, all_data):

View File

@ -0,0 +1,53 @@
"""
Convenience routines for creating non-trivial Field subclasses.
Add SubfieldBase as the __metaclass__ for your Field subclass, implement
to_python() and the other necessary methods and everything will work seamlessly.
"""
from django.utils.maxlength import LegacyMaxlength
class SubfieldBase(LegacyMaxlength):
"""
A metaclass for custom Field subclasses. This ensures the model's attribute
has the descriptor protocol attached to it.
"""
def __new__(cls, base, name, attrs):
new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs)
new_class.contribute_to_class = make_contrib(
attrs.get('contribute_to_class'))
return new_class
class Creator(object):
"""
A placeholder class that provides a way to set the attribute on the model.
"""
def __init__(self, field):
self.field = field
def __get__(self, obj, type=None):
if obj is None:
raise AttributeError('Can only be accessed via an instance.')
return self.value
def __set__(self, obj, value):
self.value = self.field.to_python(value)
def make_contrib(func=None):
"""
Returns a suitable contribute_to_class() method for the Field subclass.
If 'func' is passed in, it is the existing contribute_to_class() method on
the subclass and it is called before anything else. It is assumed in this
case that the existing contribute_to_class() calls all the necessary
superclass methods.
"""
def contribute_to_class(self, cls, name):
if func:
func(self, cls, name)
else:
super(self.__class__, self).contribute_to_class(cls, name)
setattr(cls, self.name, Creator(self))
return contribute_to_class

View File

@ -1,4 +1,4 @@
from email.Utils import formatdate from django.utils.http import http_date
class ConditionalGetMiddleware(object): class ConditionalGetMiddleware(object):
""" """
@ -11,7 +11,7 @@ class ConditionalGetMiddleware(object):
Also sets the Date and Content-Length response-headers. Also sets the Date and Content-Length response-headers.
""" """
def process_response(self, request, response): def process_response(self, request, response):
response['Date'] = formatdate()[:26] + "GMT" response['Date'] = http_date()
if not response.has_header('Content-Length'): if not response.has_header('Content-Length'):
response['Content-Length'] = str(len(response.content)) response['Content-Length'] = str(len(response.content))
@ -23,7 +23,6 @@ class ConditionalGetMiddleware(object):
response['Content-Length'] = '0' response['Content-Length'] = '0'
if response.has_header('Last-Modified'): if response.has_header('Last-Modified'):
last_mod = response['Last-Modified']
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None) if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
if if_modified_since == response['Last-Modified']: if if_modified_since == response['Last-Modified']:
response.status_code = 304 response.status_code = 304

View File

@ -1,30 +1,35 @@
""" """
Field classes Field classes.
""" """
import copy import copy
import datetime import datetime
import re import re
import time import time
# Python 2.3 fallbacks
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException
try:
set
except NameError:
from sets import Set as set
from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import StrAndUnicode, smart_unicode from django.utils.encoding import StrAndUnicode, smart_unicode
from util import ErrorList, ValidationError from util import ErrorList, ValidationError
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException
__all__ = ( __all__ = (
'Field', 'CharField', 'IntegerField', 'Field', 'CharField', 'IntegerField',
'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField', 'SplitDateTimeField', 'IPAddressField',
) )
@ -32,24 +37,20 @@ __all__ = (
# 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.
EMPTY_VALUES = (None, '') EMPTY_VALUES = (None, '')
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
try:
from decimal import Decimal
except ImportError:
from django.utils._decimal import Decimal # Python 2.3 fallback
class Field(object): class Field(object):
widget = TextInput # Default widget to use when rendering this type of Field. widget = TextInput # Default widget to use when rendering this type of Field.
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
default_error_messages = {
'required': _(u'This field is required.'),
'invalid': _(u'Enter a valid value.'),
}
# Tracks each time a Field instance is created. Used to retain order. # Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0 creation_counter = 0
def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None): def __init__(self, required=True, widget=None, label=None, initial=None,
help_text=None, error_messages=None):
# required -- Boolean that specifies whether the field is required. # required -- Boolean that specifies whether the field is required.
# True by default. # True by default.
# widget -- A Widget class, or instance of a Widget class, that should # widget -- A Widget class, or instance of a Widget class, that should
@ -82,6 +83,22 @@ class Field(object):
self.creation_counter = Field.creation_counter self.creation_counter = Field.creation_counter
Field.creation_counter += 1 Field.creation_counter += 1
self.error_messages = self._build_error_messages(error_messages)
def _build_error_messages(self, extra_error_messages):
error_messages = {}
def get_default_error_messages(klass):
for base_class in klass.__bases__:
get_default_error_messages(base_class)
if hasattr(klass, 'default_error_messages'):
error_messages.update(klass.default_error_messages)
get_default_error_messages(self.__class__)
if extra_error_messages:
error_messages.update(extra_error_messages)
return error_messages
def clean(self, value): def clean(self, value):
""" """
Validates the given value and returns its "cleaned" value as an Validates the given value and returns its "cleaned" value as an
@ -90,7 +107,7 @@ class Field(object):
Raises ValidationError for any errors. Raises ValidationError for any errors.
""" """
if self.required and value in EMPTY_VALUES: if self.required and value in EMPTY_VALUES:
raise ValidationError(ugettext(u'This field is required.')) raise ValidationError(self.error_messages['required'])
return value return value
def widget_attrs(self, widget): def widget_attrs(self, widget):
@ -108,6 +125,11 @@ class Field(object):
return result return result
class CharField(Field): class CharField(Field):
default_error_messages = {
'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'),
'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
}
def __init__(self, max_length=None, min_length=None, *args, **kwargs): def __init__(self, max_length=None, min_length=None, *args, **kwargs):
self.max_length, self.min_length = max_length, min_length self.max_length, self.min_length = max_length, min_length
super(CharField, self).__init__(*args, **kwargs) super(CharField, self).__init__(*args, **kwargs)
@ -120,9 +142,9 @@ class CharField(Field):
value = smart_unicode(value) value = smart_unicode(value)
value_length = len(value) value_length = len(value)
if self.max_length is not None and value_length > self.max_length: if self.max_length is not None and value_length > self.max_length:
raise ValidationError(ugettext(u'Ensure this value has at most %(max)d characters (it has %(length)d).') % {'max': self.max_length, 'length': value_length}) raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
if self.min_length is not None and value_length < self.min_length: if self.min_length is not None and value_length < self.min_length:
raise ValidationError(ugettext(u'Ensure this value has at least %(min)d characters (it has %(length)d).') % {'min': self.min_length, 'length': value_length}) raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
return value return value
def widget_attrs(self, widget): def widget_attrs(self, widget):
@ -131,6 +153,12 @@ class CharField(Field):
return {'maxlength': str(self.max_length)} return {'maxlength': str(self.max_length)}
class IntegerField(Field): class IntegerField(Field):
default_error_messages = {
'invalid': _(u'Enter a whole number.'),
'max_value': _(u'Ensure this value is less than or equal to %s.'),
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
}
def __init__(self, max_value=None, min_value=None, *args, **kwargs): def __init__(self, max_value=None, min_value=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value self.max_value, self.min_value = max_value, min_value
super(IntegerField, self).__init__(*args, **kwargs) super(IntegerField, self).__init__(*args, **kwargs)
@ -146,14 +174,20 @@ class IntegerField(Field):
try: try:
value = int(str(value)) value = int(str(value))
except (ValueError, TypeError): except (ValueError, TypeError):
raise ValidationError(ugettext(u'Enter a whole number.')) raise ValidationError(self.error_messages['invalid'])
if self.max_value is not None and value > self.max_value: if self.max_value is not None and value > self.max_value:
raise ValidationError(ugettext(u'Ensure this value is less than or equal to %s.') % self.max_value) raise ValidationError(self.error_messages['max_value'] % self.max_value)
if self.min_value is not None and value < self.min_value: if self.min_value is not None and value < self.min_value:
raise ValidationError(ugettext(u'Ensure this value is greater than or equal to %s.') % self.min_value) raise ValidationError(self.error_messages['min_value'] % self.min_value)
return value return value
class FloatField(Field): class FloatField(Field):
default_error_messages = {
'invalid': _(u'Enter a number.'),
'max_value': _(u'Ensure this value is less than or equal to %s.'),
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
}
def __init__(self, max_value=None, min_value=None, *args, **kwargs): def __init__(self, max_value=None, min_value=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value self.max_value, self.min_value = max_value, min_value
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
@ -169,14 +203,23 @@ class FloatField(Field):
try: try:
value = float(value) value = float(value)
except (ValueError, TypeError): except (ValueError, TypeError):
raise ValidationError(ugettext('Enter a number.')) raise ValidationError(self.error_messages['invalid'])
if self.max_value is not None and value > self.max_value: if self.max_value is not None and value > self.max_value:
raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value) raise ValidationError(self.error_messages['max_value'] % self.max_value)
if self.min_value is not None and value < self.min_value: if self.min_value is not None and value < self.min_value:
raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value) raise ValidationError(self.error_messages['min_value'] % self.min_value)
return value return value
class DecimalField(Field): class DecimalField(Field):
default_error_messages = {
'invalid': _(u'Enter a number.'),
'max_value': _(u'Ensure this value is less than or equal to %s.'),
'min_value': _(u'Ensure this value is greater than or equal to %s.'),
'max_digits': _('Ensure that there are no more than %s digits in total.'),
'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
}
def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value self.max_value, self.min_value = max_value, min_value
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
@ -196,20 +239,20 @@ class DecimalField(Field):
try: try:
value = Decimal(value) value = Decimal(value)
except DecimalException: except DecimalException:
raise ValidationError(ugettext('Enter a number.')) raise ValidationError(self.error_messages['invalid'])
pieces = str(value).lstrip("-").split('.') pieces = str(value).lstrip("-").split('.')
decimals = (len(pieces) == 2) and len(pieces[1]) or 0 decimals = (len(pieces) == 2) and len(pieces[1]) or 0
digits = len(pieces[0]) digits = len(pieces[0])
if self.max_value is not None and value > self.max_value: if self.max_value is not None and value > self.max_value:
raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value) raise ValidationError(self.error_messages['max_value'] % self.max_value)
if self.min_value is not None and value < self.min_value: if self.min_value is not None and value < self.min_value:
raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value) raise ValidationError(self.error_messages['min_value'] % self.min_value)
if self.max_digits is not None and (digits + decimals) > self.max_digits: if self.max_digits is not None and (digits + decimals) > self.max_digits:
raise ValidationError(ugettext('Ensure that there are no more than %s digits in total.') % self.max_digits) raise ValidationError(self.error_messages['max_digits'] % self.max_digits)
if self.decimal_places is not None and decimals > self.decimal_places: if self.decimal_places is not None and decimals > self.decimal_places:
raise ValidationError(ugettext('Ensure that there are no more than %s decimal places.') % self.decimal_places) raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
raise ValidationError(ugettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places)) raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
return value return value
DEFAULT_DATE_INPUT_FORMATS = ( DEFAULT_DATE_INPUT_FORMATS = (
@ -221,6 +264,10 @@ DEFAULT_DATE_INPUT_FORMATS = (
) )
class DateField(Field): class DateField(Field):
default_error_messages = {
'invalid': _(u'Enter a valid date.'),
}
def __init__(self, input_formats=None, *args, **kwargs): def __init__(self, input_formats=None, *args, **kwargs):
super(DateField, self).__init__(*args, **kwargs) super(DateField, self).__init__(*args, **kwargs)
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
@ -242,7 +289,7 @@ class DateField(Field):
return datetime.date(*time.strptime(value, format)[:3]) return datetime.date(*time.strptime(value, format)[:3])
except ValueError: except ValueError:
continue continue
raise ValidationError(ugettext(u'Enter a valid date.')) raise ValidationError(self.error_messages['invalid'])
DEFAULT_TIME_INPUT_FORMATS = ( DEFAULT_TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59' '%H:%M:%S', # '14:30:59'
@ -250,6 +297,10 @@ DEFAULT_TIME_INPUT_FORMATS = (
) )
class TimeField(Field): class TimeField(Field):
default_error_messages = {
'invalid': _(u'Enter a valid time.')
}
def __init__(self, input_formats=None, *args, **kwargs): def __init__(self, input_formats=None, *args, **kwargs):
super(TimeField, self).__init__(*args, **kwargs) super(TimeField, self).__init__(*args, **kwargs)
self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
@ -269,7 +320,7 @@ class TimeField(Field):
return datetime.time(*time.strptime(value, format)[3:6]) return datetime.time(*time.strptime(value, format)[3:6])
except ValueError: except ValueError:
continue continue
raise ValidationError(ugettext(u'Enter a valid time.')) raise ValidationError(self.error_messages['invalid'])
DEFAULT_DATETIME_INPUT_FORMATS = ( DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
@ -285,6 +336,9 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
class DateTimeField(Field): class DateTimeField(Field):
widget = DateTimeInput widget = DateTimeInput
default_error_messages = {
'invalid': _(u'Enter a valid date/time.'),
}
def __init__(self, input_formats=None, *args, **kwargs): def __init__(self, input_formats=None, *args, **kwargs):
super(DateTimeField, self).__init__(*args, **kwargs) super(DateTimeField, self).__init__(*args, **kwargs)
@ -306,14 +360,14 @@ class DateTimeField(Field):
# Input comes from a SplitDateTimeWidget, for example. So, it's two # Input comes from a SplitDateTimeWidget, for example. So, it's two
# components: date and time. # components: date and time.
if len(value) != 2: if len(value) != 2:
raise ValidationError(ugettext(u'Enter a valid date/time.')) raise ValidationError(self.error_messages['invalid'])
value = '%s %s' % tuple(value) value = '%s %s' % tuple(value)
for format in self.input_formats: for format in self.input_formats:
try: try:
return datetime.datetime(*time.strptime(value, format)[:6]) return datetime.datetime(*time.strptime(value, format)[:6])
except ValueError: except ValueError:
continue continue
raise ValidationError(ugettext(u'Enter a valid date/time.')) raise ValidationError(self.error_messages['invalid'])
class RegexField(CharField): class RegexField(CharField):
def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
@ -322,11 +376,15 @@ class RegexField(CharField):
error_message is an optional error message to use, if error_message is an optional error message to use, if
'Enter a valid value' is too generic for you. 'Enter a valid value' is too generic for you.
""" """
# error_message is just kept for backwards compatibility:
if error_message:
error_messages = kwargs.get('error_messages') or {}
error_messages['invalid'] = error_message
kwargs['error_messages'] = error_messages
super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
if isinstance(regex, basestring): if isinstance(regex, basestring):
regex = re.compile(regex) regex = re.compile(regex)
self.regex = regex self.regex = regex
self.error_message = error_message or ugettext(u'Enter a valid value.')
def clean(self, value): def clean(self, value):
""" """
@ -337,7 +395,7 @@ class RegexField(CharField):
if value == u'': if value == u'':
return value return value
if not self.regex.search(value): if not self.regex.search(value):
raise ValidationError(self.error_message) raise ValidationError(self.error_messages['invalid'])
return value return value
email_re = re.compile( email_re = re.compile(
@ -346,9 +404,13 @@ email_re = re.compile(
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
class EmailField(RegexField): class EmailField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid e-mail address.'),
}
def __init__(self, max_length=None, min_length=None, *args, **kwargs): def __init__(self, max_length=None, min_length=None, *args, **kwargs):
RegexField.__init__(self, email_re, max_length, min_length, RegexField.__init__(self, email_re, max_length, min_length, *args,
ugettext(u'Enter a valid e-mail address.'), *args, **kwargs) **kwargs)
try: try:
from django.conf import settings from django.conf import settings
@ -372,6 +434,12 @@ class UploadedFile(StrAndUnicode):
class FileField(Field): class FileField(Field):
widget = FileInput widget = FileInput
default_error_messages = {
'invalid': _(u"No file was submitted. Check the encoding type on the form."),
'missing': _(u"No file was submitted."),
'empty': _(u"The submitted file is empty."),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(FileField, self).__init__(*args, **kwargs) super(FileField, self).__init__(*args, **kwargs)
@ -382,14 +450,18 @@ class FileField(Field):
try: try:
f = UploadedFile(data['filename'], data['content']) f = UploadedFile(data['filename'], data['content'])
except TypeError: except TypeError:
raise ValidationError(ugettext(u"No file was submitted. Check the encoding type on the form.")) raise ValidationError(self.error_messages['invalid'])
except KeyError: except KeyError:
raise ValidationError(ugettext(u"No file was submitted.")) raise ValidationError(self.error_messages['missing'])
if not f.content: if not f.content:
raise ValidationError(ugettext(u"The submitted file is empty.")) raise ValidationError(self.error_messages['empty'])
return f return f
class ImageField(FileField): class ImageField(FileField):
default_error_messages = {
'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
}
def clean(self, data): def clean(self, data):
""" """
Checks that the file-upload field data contains a valid image (GIF, JPG, Checks that the file-upload field data contains a valid image (GIF, JPG,
@ -410,7 +482,7 @@ class ImageField(FileField):
trial_image = Image.open(StringIO(f.content)) trial_image = Image.open(StringIO(f.content))
trial_image.verify() trial_image.verify()
except Exception: # Python Imaging Library doesn't recognize it as an image except Exception: # Python Imaging Library doesn't recognize it as an image
raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image.")) raise ValidationError(self.error_messages['invalid_image'])
return f return f
url_re = re.compile( url_re = re.compile(
@ -422,9 +494,15 @@ url_re = re.compile(
r'(?:/?|/\S+)$', re.IGNORECASE) r'(?:/?|/\S+)$', re.IGNORECASE)
class URLField(RegexField): class URLField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid URL.'),
'invalid_link': _(u'This URL appears to be a broken link.'),
}
def __init__(self, max_length=None, min_length=None, verify_exists=False, def __init__(self, max_length=None, min_length=None, verify_exists=False,
validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
super(URLField, self).__init__(url_re, max_length, min_length, ugettext(u'Enter a valid URL.'), *args, **kwargs) super(URLField, self).__init__(url_re, max_length, min_length, *args,
**kwargs)
self.verify_exists = verify_exists self.verify_exists = verify_exists
self.user_agent = validator_user_agent self.user_agent = validator_user_agent
@ -449,9 +527,9 @@ class URLField(RegexField):
req = urllib2.Request(value, None, headers) req = urllib2.Request(value, None, headers)
u = urllib2.urlopen(req) u = urllib2.urlopen(req)
except ValueError: except ValueError:
raise ValidationError(ugettext(u'Enter a valid URL.')) raise ValidationError(self.error_messages['invalid'])
except: # urllib2.URLError, httplib.InvalidURL, etc. except: # urllib2.URLError, httplib.InvalidURL, etc.
raise ValidationError(ugettext(u'This URL appears to be a broken link.')) raise ValidationError(self.error_messages['invalid_link'])
return value return value
class BooleanField(Field): class BooleanField(Field):
@ -478,9 +556,14 @@ class NullBooleanField(BooleanField):
class ChoiceField(Field): class ChoiceField(Field):
widget = Select widget = Select
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'),
}
def __init__(self, choices=(), required=True, widget=None, label=None, initial=None, help_text=None): def __init__(self, choices=(), required=True, widget=None, label=None,
super(ChoiceField, self).__init__(required, widget, label, initial, help_text) initial=None, help_text=None, *args, **kwargs):
super(ChoiceField, self).__init__(required, widget, label, initial,
help_text, *args, **kwargs)
self.choices = choices self.choices = choices
def _get_choices(self): def _get_choices(self):
@ -506,29 +589,33 @@ class ChoiceField(Field):
return value return value
valid_values = set([smart_unicode(k) for k, v in self.choices]) valid_values = set([smart_unicode(k) for k, v in self.choices])
if value not in valid_values: if value not in valid_values:
raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.')) raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
return value return value
class MultipleChoiceField(ChoiceField): class MultipleChoiceField(ChoiceField):
hidden_widget = MultipleHiddenInput hidden_widget = MultipleHiddenInput
widget = SelectMultiple widget = SelectMultiple
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
'invalid_list': _(u'Enter a list of values.'),
}
def clean(self, value): def clean(self, value):
""" """
Validates that the input is a list or tuple. Validates that the input is a list or tuple.
""" """
if self.required and not value: if self.required and not value:
raise ValidationError(ugettext(u'This field is required.')) raise ValidationError(self.error_messages['required'])
elif not self.required and not value: elif not self.required and not value:
return [] return []
if not isinstance(value, (list, tuple)): if not isinstance(value, (list, tuple)):
raise ValidationError(ugettext(u'Enter a list of values.')) raise ValidationError(self.error_messages['invalid_list'])
new_value = [smart_unicode(val) for val in value] new_value = [smart_unicode(val) for val in value]
# Validate that each value in the value list is in self.choices. # Validate that each value in the value list is in self.choices.
valid_values = set([smart_unicode(k) for k, v in self.choices]) valid_values = set([smart_unicode(k) for k, v in self.choices])
for val in new_value: for val in new_value:
if val not in valid_values: if val not in valid_values:
raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val) raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
return new_value return new_value
class ComboField(Field): class ComboField(Field):
@ -571,6 +658,10 @@ class MultiValueField(Field):
You'll probably want to use this with MultiWidget. You'll probably want to use this with MultiWidget.
""" """
default_error_messages = {
'invalid': _(u'Enter a list of values.'),
}
def __init__(self, fields=(), *args, **kwargs): def __init__(self, fields=(), *args, **kwargs):
super(MultiValueField, self).__init__(*args, **kwargs) super(MultiValueField, self).__init__(*args, **kwargs)
# Set 'required' to False on the individual fields, because the # Set 'required' to False on the individual fields, because the
@ -594,18 +685,18 @@ class MultiValueField(Field):
if not value or isinstance(value, (list, tuple)): if not value or isinstance(value, (list, tuple)):
if not value or not [v for v in value if v not in EMPTY_VALUES]: if not value or not [v for v in value if v not in EMPTY_VALUES]:
if self.required: if self.required:
raise ValidationError(ugettext(u'This field is required.')) raise ValidationError(self.error_messages['required'])
else: else:
return self.compress([]) return self.compress([])
else: else:
raise ValidationError(ugettext(u'Enter a list of values.')) raise ValidationError(self.error_messages['invalid'])
for i, field in enumerate(self.fields): for i, field in enumerate(self.fields):
try: try:
field_value = value[i] field_value = value[i]
except IndexError: except IndexError:
field_value = None field_value = None
if self.required and field_value in EMPTY_VALUES: if self.required and field_value in EMPTY_VALUES:
raise ValidationError(ugettext(u'This field is required.')) raise ValidationError(self.error_messages['required'])
try: try:
clean_data.append(field.clean(field_value)) clean_data.append(field.clean(field_value))
except ValidationError, e: except ValidationError, e:
@ -629,8 +720,19 @@ class MultiValueField(Field):
raise NotImplementedError('Subclasses must implement this method.') raise NotImplementedError('Subclasses must implement this method.')
class SplitDateTimeField(MultiValueField): class SplitDateTimeField(MultiValueField):
default_error_messages = {
'invalid_date': _(u'Enter a valid date.'),
'invalid_time': _(u'Enter a valid time.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
fields = (DateField(), TimeField()) errors = self.default_error_messages.copy()
if 'error_messages' in kwargs:
errors.update(kwargs['error_messages'])
fields = (
DateField(error_messages={'invalid': errors['invalid_date']}),
TimeField(error_messages={'invalid': errors['invalid_time']}),
)
super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list): def compress(self, data_list):
@ -638,16 +740,18 @@ class SplitDateTimeField(MultiValueField):
# Raise a validation error if time or date is empty # Raise a validation error if time or date is empty
# (possible if SplitDateTimeField has required=False). # (possible if SplitDateTimeField has required=False).
if data_list[0] in EMPTY_VALUES: if data_list[0] in EMPTY_VALUES:
raise ValidationError(ugettext(u'Enter a valid date.')) raise ValidationError(self.error_messages['invalid_date'])
if data_list[1] in EMPTY_VALUES: if data_list[1] in EMPTY_VALUES:
raise ValidationError(ugettext(u'Enter a valid time.')) raise ValidationError(self.error_messages['invalid_time'])
return datetime.datetime.combine(*data_list) return datetime.datetime.combine(*data_list)
return None return None
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
class IPAddressField(RegexField): class IPAddressField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid IPv4 address.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
RegexField.__init__(self, ipv4_re, super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
error_message=ugettext(u'Enter a valid IPv4 address.'),
*args, **kwargs)

View File

@ -6,7 +6,6 @@ and database field objects.
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from util import ValidationError from util import ValidationError
from forms import BaseForm, SortedDictFromList from forms import BaseForm, SortedDictFromList
from fields import Field, ChoiceField, IntegerField from fields import Field, ChoiceField, IntegerField
@ -19,7 +18,8 @@ __all__ = (
'ModelChoiceField', 'ModelMultipleChoiceField', 'ModelChoiceField', 'ModelMultipleChoiceField',
) )
def save_instance(form, instance, fields=None, fail_message='saved', commit=True): def save_instance(form, instance, fields=None, fail_message='saved',
commit=True):
""" """
Saves bound Form ``form``'s cleaned_data into model instance ``instance``. Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
@ -29,15 +29,17 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
from django.db import models from django.db import models
opts = instance.__class__._meta opts = instance.__class__._meta
if form.errors: if form.errors:
raise ValueError("The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message)) raise ValueError("The %s could not be %s because the data didn't"
" validate." % (opts.object_name, fail_message))
cleaned_data = form.cleaned_data cleaned_data = form.cleaned_data
for f in opts.fields: for f in opts.fields:
if not f.editable or isinstance(f, models.AutoField) or not f.name in cleaned_data: if not f.editable or isinstance(f, models.AutoField) \
or not f.name in cleaned_data:
continue continue
if fields and f.name not in fields: if fields and f.name not in fields:
continue continue
f.save_form_data(instance, cleaned_data[f.name]) f.save_form_data(instance, cleaned_data[f.name])
# Wrap up the saving of m2m data as a function # Wrap up the saving of m2m data as a function.
def save_m2m(): def save_m2m():
opts = instance.__class__._meta opts = instance.__class__._meta
cleaned_data = form.cleaned_data cleaned_data = form.cleaned_data
@ -47,28 +49,29 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
if f.name in cleaned_data: if f.name in cleaned_data:
f.save_form_data(instance, cleaned_data[f.name]) f.save_form_data(instance, cleaned_data[f.name])
if commit: if commit:
# If we are committing, save the instance and the m2m data immediately # If we are committing, save the instance and the m2m data immediately.
instance.save() instance.save()
save_m2m() save_m2m()
else: else:
# We're not committing. Add a method to the form to allow deferred # We're not committing. Add a method to the form to allow deferred
# saving of m2m data # saving of m2m data.
form.save_m2m = save_m2m form.save_m2m = save_m2m
return instance return instance
def make_model_save(model, fields, fail_message): def make_model_save(model, fields, fail_message):
"Returns the save() method for a Form." """Returns the save() method for a Form."""
def save(self, commit=True): def save(self, commit=True):
return save_instance(self, model(), fields, fail_message, commit) return save_instance(self, model(), fields, fail_message, commit)
return save return save
def make_instance_save(instance, fields, fail_message): def make_instance_save(instance, fields, fail_message):
"Returns the save() method for a Form." """Returns the save() method for a Form."""
def save(self, commit=True): def save(self, commit=True):
return save_instance(self, instance, fields, fail_message, commit) return save_instance(self, instance, fields, fail_message, commit)
return save return save
def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda f: f.formfield()): def form_for_model(model, form=BaseForm, fields=None,
formfield_callback=lambda f: f.formfield()):
""" """
Returns a Form class for the given Django model class. Returns a Form class for the given Django model class.
@ -90,9 +93,11 @@ def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list) base_fields = SortedDictFromList(field_list)
return type(opts.object_name + 'Form', (form,), return type(opts.object_name + 'Form', (form,),
{'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')}) {'base_fields': base_fields, '_model': model,
'save': make_model_save(model, fields, 'created')})
def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): def form_for_instance(instance, form=BaseForm, fields=None,
formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
""" """
Returns a Form class for the given Django model instance. Returns a Form class for the given Django model instance.
@ -117,16 +122,22 @@ def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=l
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list) base_fields = SortedDictFromList(field_list)
return type(opts.object_name + 'InstanceForm', (form,), return type(opts.object_name + 'InstanceForm', (form,),
{'base_fields': base_fields, '_model': model, 'save': make_instance_save(instance, fields, 'changed')}) {'base_fields': base_fields, '_model': model,
'save': make_instance_save(instance, fields, 'changed')})
def form_for_fields(field_list): def form_for_fields(field_list):
"Returns a Form class for the given list of Django database field instances." """
fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable]) Returns a Form class for the given list of Django database field instances.
"""
fields = SortedDictFromList([(f.name, f.formfield())
for f in field_list if f.editable])
return type('FormForFields', (BaseForm,), {'base_fields': fields}) return type('FormForFields', (BaseForm,), {'base_fields': fields})
class QuerySetIterator(object): class QuerySetIterator(object):
def __init__(self, queryset, empty_label, cache_choices): def __init__(self, queryset, empty_label, cache_choices):
self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices self.queryset = queryset
self.empty_label = empty_label
self.cache_choices = cache_choices
def __iter__(self): def __iter__(self):
if self.empty_label is not None: if self.empty_label is not None:
@ -138,11 +149,13 @@ class QuerySetIterator(object):
self.queryset._result_cache = None self.queryset._result_cache = None
class ModelChoiceField(ChoiceField): class ModelChoiceField(ChoiceField):
"A ChoiceField whose choices are a model QuerySet." """A ChoiceField whose choices are a model QuerySet."""
# This class is a subclass of ChoiceField for purity, but it doesn't # This class is a subclass of ChoiceField for purity, but it doesn't
# actually use any of ChoiceField's implementation. # actually use any of ChoiceField's implementation.
def __init__(self, queryset, empty_label=u"---------", cache_choices=False, def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
required=True, widget=Select, label=None, initial=None, help_text=None): required=True, widget=Select, label=None, initial=None,
help_text=None):
self.queryset = queryset self.queryset = queryset
self.empty_label = empty_label self.empty_label = empty_label
self.cache_choices = cache_choices self.cache_choices = cache_choices
@ -162,7 +175,8 @@ class ModelChoiceField(ChoiceField):
# *each* time _get_choices() is called (and, thus, each time # *each* time _get_choices() is called (and, thus, each time
# self.choices is accessed) so that we can ensure the QuerySet has not # self.choices is accessed) so that we can ensure the QuerySet has not
# been consumed. # been consumed.
return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices) return QuerySetIterator(self.queryset, self.empty_label,
self.cache_choices)
def _set_choices(self, value): def _set_choices(self, value):
# This method is copied from ChoiceField._set_choices(). It's necessary # This method is copied from ChoiceField._set_choices(). It's necessary
@ -179,16 +193,20 @@ class ModelChoiceField(ChoiceField):
try: try:
value = self.queryset.model._default_manager.get(pk=value) value = self.queryset.model._default_manager.get(pk=value)
except self.queryset.model.DoesNotExist: except self.queryset.model.DoesNotExist:
raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.')) raise ValidationError(ugettext(u'Select a valid choice. That'
u' choice is not one of the'
u' available choices.'))
return value return value
class ModelMultipleChoiceField(ModelChoiceField): class ModelMultipleChoiceField(ModelChoiceField):
"A MultipleChoiceField whose choices are a model QuerySet." """A MultipleChoiceField whose choices are a model QuerySet."""
hidden_widget = MultipleHiddenInput hidden_widget = MultipleHiddenInput
def __init__(self, queryset, cache_choices=False, required=True, def __init__(self, queryset, cache_choices=False, required=True,
widget=SelectMultiple, label=None, initial=None, help_text=None): widget=SelectMultiple, label=None, initial=None,
super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices, help_text=None):
required, widget, label, initial, help_text) super(ModelMultipleChoiceField, self).__init__(queryset, None,
cache_choices, required, widget, label, initial, help_text)
def clean(self, value): def clean(self, value):
if self.required and not value: if self.required and not value:
@ -202,7 +220,9 @@ class ModelMultipleChoiceField(ModelChoiceField):
try: try:
obj = self.queryset.model._default_manager.get(pk=val) obj = self.queryset.model._default_manager.get(pk=val)
except self.queryset.model.DoesNotExist: except self.queryset.model.DoesNotExist:
raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val) raise ValidationError(ugettext(u'Select a valid choice. %s is'
u' not one of the available'
u' choices.') % val)
else: else:
final_values.append(obj) final_values.append(obj)
return final_values return final_values

View File

@ -42,13 +42,18 @@ class ErrorList(list, StrAndUnicode):
if not self: return u'' if not self: return u''
return u'\n'.join([u'* %s' % force_unicode(e) for e in self]) return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
def __repr__(self):
return repr([force_unicode(e) for e in self])
class ValidationError(Exception): class ValidationError(Exception):
def __init__(self, message): def __init__(self, message):
"ValidationError can be passed a string or a list." """
ValidationError can be passed any object that can be printed (usually
a string) or a list of objects.
"""
if isinstance(message, list): if isinstance(message, list):
self.messages = ErrorList([smart_unicode(msg) for msg in message]) self.messages = ErrorList([smart_unicode(msg) for msg in message])
else: else:
assert isinstance(message, (basestring, Promise)), ("%s should be a basestring or lazy translation" % repr(message))
message = smart_unicode(message) message = smart_unicode(message)
self.messages = ErrorList([message]) self.messages = ErrorList([message])
@ -58,4 +63,3 @@ class ValidationError(Exception):
# AttributeError: ValidationError instance has no attribute 'args' # AttributeError: ValidationError instance has no attribute 'args'
# See http://www.python.org/doc/current/tut/node10.html#handling # See http://www.python.org/doc/current/tut/node10.html#handling
return repr(self.messages) return repr(self.messages)

View File

@ -1,11 +1,12 @@
"Default variable filters" """Default variable filters."""
import re
import random as random_module
from django.template import Variable, Library from django.template import Variable, Library
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext, ungettext from django.utils.translation import ugettext, ungettext
from django.utils.encoding import force_unicode, smart_str, iri_to_uri from django.utils.encoding import force_unicode, iri_to_uri
import re
import random as random_module
register = Library() register = Library()
@ -36,39 +37,48 @@ def stringfilter(func):
def addslashes(value): def addslashes(value):
"Adds slashes - useful for passing strings to JavaScript, for example." """Adds slashes - useful for passing strings to JavaScript, for example."""
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
addslashes = stringfilter(addslashes) addslashes = stringfilter(addslashes)
def capfirst(value): def capfirst(value):
"Capitalizes the first character of the value" """Capitalizes the first character of the value."""
return value and value[0].upper() + value[1:] return value and value[0].upper() + value[1:]
capfirst = stringfilter(capfirst) capfirst = stringfilter(capfirst)
def fix_ampersands(value): def fix_ampersands(value):
"Replaces ampersands with ``&amp;`` entities" """Replaces ampersands with ``&amp;`` entities."""
from django.utils.html import fix_ampersands from django.utils.html import fix_ampersands
return fix_ampersands(value) return fix_ampersands(value)
fix_ampersands = stringfilter(fix_ampersands) fix_ampersands = stringfilter(fix_ampersands)
def floatformat(text, arg=-1): def floatformat(text, arg=-1):
""" """
If called without an argument, displays a floating point Displays a float to a specified number of decimal places.
number as 34.2 -- but only if there's a point to be displayed.
With a positive numeric argument, it displays that many decimal places If called without an argument, it displays the floating point number with
always. one decimal place -- but only if there's a decimal place to be displayed:
With a negative numeric argument, it will display that many decimal
places -- but only if there's places to be displayed.
Examples:
* num1 = 34.23234 * num1 = 34.23234
* num2 = 34.00000 * num2 = 34.00000
* num1|floatformat results in 34.2 * num3 = 34.26000
* num2|floatformat is 34 * {{ num1|floatformat }} displays "34.2"
* num1|floatformat:3 is 34.232 * {{ num2|floatformat }} displays "34"
* num2|floatformat:3 is 34.000 * {{ num3|floatformat }} displays "34.3"
* num1|floatformat:-3 is 34.232
* num2|floatformat:-3 is 34 If arg is positive, it will always display exactly arg number of decimal
places:
* {{ num1|floatformat:3 }} displays "34.232"
* {{ num2|floatformat:3 }} displays "34.000"
* {{ num3|floatformat:3 }} displays "34.260"
If arg is negative, it will display arg number of decimal places -- but
only if there are places to be displayed:
* {{ num1|floatformat:"-3" }} displays "34.232"
* {{ num2|floatformat:"-3" }} displays "34"
* {{ num3|floatformat:"-3" }} displays "34.260"
""" """
try: try:
f = float(text) f = float(text)
@ -86,15 +96,16 @@ def floatformat(text, arg=-1):
return formatstr % f return formatstr % f
def iriencode(value): def iriencode(value):
"Escapes an IRI value for use in a URL" """Escapes an IRI value for use in a URL."""
return force_unicode(iri_to_uri(value)) return force_unicode(iri_to_uri(value))
iriencode = stringfilter(iriencode) iriencode = stringfilter(iriencode)
def linenumbers(value): def linenumbers(value):
"Displays text with line numbers" """Displays text with line numbers."""
from django.utils.html import escape from django.utils.html import escape
lines = value.split(u'\n') lines = value.split(u'\n')
# Find the maximum width of the line count, for use with zero padding string format command # Find the maximum width of the line count, for use with zero padding
# string format command.
width = unicode(len(unicode(len(lines)))) width = unicode(len(unicode(len(lines))))
for i, line in enumerate(lines): for i, line in enumerate(lines):
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
@ -102,22 +113,24 @@ def linenumbers(value):
linenumbers = stringfilter(linenumbers) linenumbers = stringfilter(linenumbers)
def lower(value): def lower(value):
"Converts a string into all lowercase" """Converts a string into all lowercase."""
return value.lower() return value.lower()
lower = stringfilter(lower) lower = stringfilter(lower)
def make_list(value): def make_list(value):
""" """
Returns the value turned into a list. For an integer, it's a list of Returns the value turned into a list.
digits. For a string, it's a list of characters.
For an integer, it's a list of digits.
For a string, it's a list of characters.
""" """
return list(value) return list(value)
make_list = stringfilter(make_list) make_list = stringfilter(make_list)
def slugify(value): def slugify(value):
""" """
Normalizes string, converts to lowercase, removes non-alpha chars and Normalizes string, converts to lowercase, removes non-alpha characters,
converts spaces to hyphens. and converts spaces to hyphens.
""" """
import unicodedata import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
@ -127,7 +140,8 @@ slugify = stringfilter(slugify)
def stringformat(value, arg): def stringformat(value, arg):
""" """
Formats the variable according to the argument, a string formatting specifier. Formats the variable according to the arg, a string formatting specifier.
This specifier uses Python string formating syntax, with the exception that This specifier uses Python string formating syntax, with the exception that
the leading "%" is dropped. the leading "%" is dropped.
@ -140,29 +154,29 @@ def stringformat(value, arg):
return u"" return u""
def title(value): def title(value):
"Converts a string into titlecase" """Converts a string into titlecase."""
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
title = stringfilter(title) title = stringfilter(title)
def truncatewords(value, arg): def truncatewords(value, arg):
""" """
Truncates a string after a certain number of words Truncates a string after a certain number of words.
Argument: Number of words to truncate after Argument: Number of words to truncate after.
""" """
from django.utils.text import truncate_words from django.utils.text import truncate_words
try: try:
length = int(arg) length = int(arg)
except ValueError: # invalid literal for int() except ValueError: # Invalid literal for int().
return value # Fail silently. return value # Fail silently.
return truncate_words(value, length) return truncate_words(value, length)
truncatewords = stringfilter(truncatewords) truncatewords = stringfilter(truncatewords)
def truncatewords_html(value, arg): def truncatewords_html(value, arg):
""" """
Truncates HTML after a certain number of words Truncates HTML after a certain number of words.
Argument: Number of words to truncate after Argument: Number of words to truncate after.
""" """
from django.utils.text import truncate_html_words from django.utils.text import truncate_html_words
try: try:
@ -173,26 +187,26 @@ def truncatewords_html(value, arg):
truncatewords_html = stringfilter(truncatewords_html) truncatewords_html = stringfilter(truncatewords_html)
def upper(value): def upper(value):
"Converts a string into all uppercase" """Converts a string into all uppercase."""
return value.upper() return value.upper()
upper = stringfilter(upper) upper = stringfilter(upper)
def urlencode(value): def urlencode(value):
"Escapes a value for use in a URL" """Escapes a value for use in a URL."""
from django.utils.http import urlquote from django.utils.http import urlquote
return urlquote(value) return urlquote(value)
urlencode = stringfilter(urlencode) urlencode = stringfilter(urlencode)
def urlize(value): def urlize(value):
"Converts URLs in plain text into clickable links" """Converts URLs in plain text into clickable links."""
from django.utils.html import urlize from django.utils.html import urlize
return urlize(value, nofollow=True) return urlize(value, nofollow=True)
urlize = stringfilter(urlize) urlize = stringfilter(urlize)
def urlizetrunc(value, limit): def urlizetrunc(value, limit):
""" """
Converts URLs into clickable links, truncating URLs to the given character limit, Converts URLs into clickable links, truncating URLs to the given character
and adding 'rel=nofollow' attribute to discourage spamming. limit, and adding 'rel=nofollow' attribute to discourage spamming.
Argument: Length to truncate URLs to. Argument: Length to truncate URLs to.
""" """
@ -201,13 +215,13 @@ def urlizetrunc(value, limit):
urlizetrunc = stringfilter(urlizetrunc) urlizetrunc = stringfilter(urlizetrunc)
def wordcount(value): def wordcount(value):
"Returns the number of words" """Returns the number of words."""
return len(value.split()) return len(value.split())
wordcount = stringfilter(wordcount) wordcount = stringfilter(wordcount)
def wordwrap(value, arg): def wordwrap(value, arg):
""" """
Wraps words at specified line length Wraps words at specified line length.
Argument: number of characters to wrap the text at. Argument: number of characters to wrap the text at.
""" """
@ -217,29 +231,29 @@ wordwrap = stringfilter(wordwrap)
def ljust(value, arg): def ljust(value, arg):
""" """
Left-aligns the value in a field of a given width Left-aligns the value in a field of a given width.
Argument: field size Argument: field size.
""" """
return value.ljust(int(arg)) return value.ljust(int(arg))
ljust = stringfilter(ljust) ljust = stringfilter(ljust)
def rjust(value, arg): def rjust(value, arg):
""" """
Right-aligns the value in a field of a given width Right-aligns the value in a field of a given width.
Argument: field size Argument: field size.
""" """
return value.rjust(int(arg)) return value.rjust(int(arg))
rjust = stringfilter(rjust) rjust = stringfilter(rjust)
def center(value, arg): def center(value, arg):
"Centers the value in a field of a given width" """Centers the value in a field of a given width."""
return value.center(int(arg)) return value.center(int(arg))
center = stringfilter(center) center = stringfilter(center)
def cut(value, arg): def cut(value, arg):
"Removes all values of arg from the given string" """Removes all values of arg from the given string."""
return value.replace(arg, u'') return value.replace(arg, u'')
cut = stringfilter(cut) cut = stringfilter(cut)
@ -272,7 +286,7 @@ def linebreaksbr(value):
linebreaksbr = stringfilter(linebreaksbr) linebreaksbr = stringfilter(linebreaksbr)
def removetags(value, tags): def removetags(value, tags):
"Removes a space separated list of [X]HTML tags from the output" """Removes a space separated list of [X]HTML tags from the output."""
tags = [re.escape(tag) for tag in tags.split()] tags = [re.escape(tag) for tag in tags.split()]
tags_re = u'(%s)' % u'|'.join(tags) tags_re = u'(%s)' % u'|'.join(tags)
starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
@ -283,7 +297,7 @@ def removetags(value, tags):
removetags = stringfilter(removetags) removetags = stringfilter(removetags)
def striptags(value): def striptags(value):
"Strips all [X]HTML tags" """Strips all [X]HTML tags."""
from django.utils.html import strip_tags from django.utils.html import strip_tags
return strip_tags(value) return strip_tags(value)
striptags = stringfilter(striptags) striptags = stringfilter(striptags)
@ -314,29 +328,29 @@ def dictsortreversed(value, arg):
return [item[1] for item in decorated] return [item[1] for item in decorated]
def first(value): def first(value):
"Returns the first item in a list" """Returns the first item in a list."""
try: try:
return value[0] return value[0]
except IndexError: except IndexError:
return u'' return u''
def join(value, arg): def join(value, arg):
"Joins a list with a string, like Python's ``str.join(list)``" """Joins a list with a string, like Python's ``str.join(list)``."""
try: try:
return arg.join(map(force_unicode, value)) return arg.join(map(force_unicode, value))
except AttributeError: # fail silently but nicely except AttributeError: # fail silently but nicely
return value return value
def length(value): def length(value):
"Returns the length of the value - useful for lists" """Returns the length of the value - useful for lists."""
return len(value) return len(value)
def length_is(value, arg): def length_is(value, arg):
"Returns a boolean of whether the value's length is the argument" """Returns a boolean of whether the value's length is the argument."""
return len(value) == int(arg) return len(value) == int(arg)
def random(value): def random(value):
"Returns a random item from the list" """Returns a random item from the list."""
return random_module.choice(value) return random_module.choice(value)
def slice_(value, arg): def slice_(value, arg):
@ -416,7 +430,7 @@ def unordered_list(value):
sublist = '' sublist = ''
sublist_item = None sublist_item = None
if isinstance(title, (list, tuple)): if isinstance(title, (list, tuple)):
sublist_item = title sublist_item = title
title = '' title = ''
elif i < list_length - 1: elif i < list_length - 1:
next_item = list_[i+1] next_item = list_[i+1]
@ -424,7 +438,7 @@ def unordered_list(value):
# The next item is a sub-list. # The next item is a sub-list.
sublist_item = next_item sublist_item = next_item
# We've processed the next item now too. # We've processed the next item now too.
i += 1 i += 1
if sublist_item: if sublist_item:
sublist = _helper(sublist_item, tabs+1) sublist = _helper(sublist_item, tabs+1)
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist, sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
@ -433,7 +447,7 @@ def unordered_list(value):
sublist)) sublist))
i += 1 i += 1
return '\n'.join(output) return '\n'.join(output)
value, converted = convert_old_style_list(value) value, converted = convert_old_style_list(value)
return _helper(value) return _helper(value)
################### ###################
@ -441,7 +455,7 @@ def unordered_list(value):
################### ###################
def add(value, arg): def add(value, arg):
"Adds the arg to the value" """Adds the arg to the value."""
return int(value) + int(arg) return int(value) + int(arg)
def get_digit(value, arg): def get_digit(value, arg):
@ -468,7 +482,7 @@ def get_digit(value, arg):
################### ###################
def date(value, arg=None): def date(value, arg=None):
"Formats a date according to the given format" """Formats a date according to the given format."""
from django.utils.dateformat import format from django.utils.dateformat import format
if not value: if not value:
return u'' return u''
@ -477,7 +491,7 @@ def date(value, arg=None):
return format(value, arg) return format(value, arg)
def time(value, arg=None): def time(value, arg=None):
"Formats a time according to the given format" """Formats a time according to the given format."""
from django.utils.dateformat import time_format from django.utils.dateformat import time_format
if value in (None, u''): if value in (None, u''):
return u'' return u''
@ -486,7 +500,7 @@ def time(value, arg=None):
return time_format(value, arg) return time_format(value, arg)
def timesince(value, arg=None): def timesince(value, arg=None):
'Formats a date as the time since that date (i.e. "4 days, 6 hours")' """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
from django.utils.timesince import timesince from django.utils.timesince import timesince
if not value: if not value:
return u'' return u''
@ -495,7 +509,7 @@ def timesince(value, arg=None):
return timesince(value) return timesince(value)
def timeuntil(value, arg=None): def timeuntil(value, arg=None):
'Formats a date as the time until that date (i.e. "4 days, 6 hours")' """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
from django.utils.timesince import timesince from django.utils.timesince import timesince
from datetime import datetime from datetime import datetime
if not value: if not value:
@ -509,17 +523,17 @@ def timeuntil(value, arg=None):
################### ###################
def default(value, arg): def default(value, arg):
"If value is unavailable, use given default" """If value is unavailable, use given default."""
return value or arg return value or arg
def default_if_none(value, arg): def default_if_none(value, arg):
"If value is None, use given default" """If value is None, use given default."""
if value is None: if value is None:
return arg return arg
return value return value
def divisibleby(value, arg): def divisibleby(value, arg):
"Returns true if the value is devisible by the argument" """Returns True if the value is devisible by the argument."""
return int(value) % int(arg) == 0 return int(value) % int(arg) == 0
def yesno(value, arg=None): def yesno(value, arg=None):
@ -544,7 +558,8 @@ def yesno(value, arg=None):
return value # Invalid arg. return value # Invalid arg.
try: try:
yes, no, maybe = bits yes, no, maybe = bits
except ValueError: # unpack list of wrong size (no "maybe" value provided) except ValueError:
# Unpack list of wrong size (no "maybe" value provided).
yes, no, maybe = bits[0], bits[1], bits[1] yes, no, maybe = bits[0], bits[1], bits[1]
if value is None: if value is None:
return maybe return maybe
@ -558,8 +573,8 @@ def yesno(value, arg=None):
def filesizeformat(bytes): def filesizeformat(bytes):
""" """
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
bytes, etc). 102 bytes, etc).
""" """
try: try:
bytes = float(bytes) bytes = float(bytes)
@ -576,10 +591,26 @@ def filesizeformat(bytes):
def pluralize(value, arg=u's'): def pluralize(value, arg=u's'):
""" """
Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes' Returns a plural suffix if the value is not 1. By default, 's' is used as
By default, 's' is used as a suffix; if an argument is provided, that string the suffix:
is used instead. If the provided argument contains a comma, the text before
the comma is used for the singular case. * If value is 0, vote{{ value|plurlize }} displays "0 votes".
* If value is 1, vote{{ value|plurlize }} displays "1 vote".
* If value is 2, vote{{ value|plurlize }} displays "2 votes".
If an argument is provided, that string is used instead:
* If value is 0, class{{ value|plurlize:"es" }} displays "0 classes".
* If value is 1, class{{ value|plurlize:"es" }} displays "1 class".
* If value is 2, class{{ value|plurlize:"es" }} displays "2 classes".
If the provided argument contains a comma, the text before the comma is
used for the singular case and the text after the comma is used for the
plural case:
* If value is 0, cand{{ value|plurlize:"y,ies" }} displays "0 candies".
* If value is 1, cand{{ value|plurlize:"y,ies" }} displays "1 candy".
* If value is 2, cand{{ value|plurlize:"y,ies" }} displays "2 candies".
""" """
if not u',' in arg: if not u',' in arg:
arg = u',' + arg arg = u',' + arg
@ -591,23 +622,23 @@ def pluralize(value, arg=u's'):
try: try:
if int(value) != 1: if int(value) != 1:
return plural_suffix return plural_suffix
except ValueError: # invalid string that's not a number except ValueError: # Invalid string that's not a number.
pass pass
except TypeError: # value isn't a string or a number; maybe it's a list? except TypeError: # Value isn't a string or a number; maybe it's a list?
try: try:
if len(value) != 1: if len(value) != 1:
return plural_suffix return plural_suffix
except TypeError: # len() of unsized object except TypeError: # len() of unsized object.
pass pass
return singular_suffix return singular_suffix
def phone2numeric(value): def phone2numeric(value):
"Takes a phone number and converts it in to its numerical equivalent" """Takes a phone number and converts it in to its numerical equivalent."""
from django.utils.text import phone2numeric from django.utils.text import phone2numeric
return phone2numeric(value) return phone2numeric(value)
def pprint(value): def pprint(value):
"A wrapper around pprint.pprint -- for debugging, really" """A wrapper around pprint.pprint -- for debugging, really."""
from pprint import pformat from pprint import pformat
try: try:
return pformat(value) return pformat(value)

View File

@ -1,4 +1,12 @@
"Default tags used by the template system, available to all templates." """Default tags used by the template system, available to all templates."""
import sys
import re
from itertools import cycle as itertools_cycle
try:
reversed
except NameError:
from django.utils.itercompat import reversed # Python 2.3 fallback
from django.template import Node, NodeList, Template, Context, Variable from django.template import Node, NodeList, Template, Context, Variable
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
@ -6,13 +14,6 @@ from django.template import get_library, Library, InvalidTemplateLibrary
from django.conf import settings from django.conf import settings
from django.utils.encoding import smart_str, smart_unicode from django.utils.encoding import smart_str, smart_unicode
from django.utils.itercompat import groupby from django.utils.itercompat import groupby
import sys
import re
try:
reversed
except NameError:
from django.utils.itercompat import reversed # Python 2.3 fallback
register = Library() register = Library()
@ -22,14 +23,11 @@ class CommentNode(Node):
class CycleNode(Node): class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None): def __init__(self, cyclevars, variable_name=None):
self.cyclevars = cyclevars self.cycle_iter = itertools_cycle(cyclevars)
self.cyclevars_len = len(cyclevars)
self.counter = -1
self.variable_name = variable_name self.variable_name = variable_name
def render(self, context): def render(self, context):
self.counter += 1 value = self.cycle_iter.next()
value = self.cyclevars[self.counter % self.cyclevars_len]
value = Variable(value).resolve(context) value = Variable(value).resolve(context)
if self.variable_name: if self.variable_name:
context[self.variable_name] = value context[self.variable_name] = value
@ -49,7 +47,7 @@ class FilterNode(Node):
def render(self, context): def render(self, context):
output = self.nodelist.render(context) output = self.nodelist.render(context)
# apply filters # Apply filters.
context.update({'var': output}) context.update({'var': output})
filtered = self.filter_expr.resolve(context) filtered = self.filter_expr.resolve(context)
context.pop() context.pop()
@ -81,7 +79,8 @@ class ForNode(Node):
else: else:
reversed = '' reversed = ''
return "<For Node: for %s in %s, tail_len: %d%s>" % \ return "<For Node: for %s in %s, tail_len: %d%s>" % \
(', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed) (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
reversed)
def __iter__(self): def __iter__(self):
for node in self.nodelist_loop: for node in self.nodelist_loop:
@ -115,19 +114,20 @@ class ForNode(Node):
unpack = len(self.loopvars) > 1 unpack = len(self.loopvars) > 1
for i, item in enumerate(values): for i, item in enumerate(values):
context['forloop'] = { context['forloop'] = {
# shortcuts for current loop iteration number # Shortcuts for current loop iteration number.
'counter0': i, 'counter0': i,
'counter': i+1, 'counter': i+1,
# reverse counter iteration numbers # Reverse counter iteration numbers.
'revcounter': len_values - i, 'revcounter': len_values - i,
'revcounter0': len_values - i - 1, 'revcounter0': len_values - i - 1,
# boolean values designating first and last times through loop # Boolean values designating first and last times through loop.
'first': (i == 0), 'first': (i == 0),
'last': (i == len_values - 1), 'last': (i == len_values - 1),
'parentloop': parentloop, 'parentloop': parentloop,
} }
if unpack: if unpack:
# If there are multiple loop variables, unpack the item into them. # If there are multiple loop variables, unpack the item into
# them.
context.update(dict(zip(self.loopvars, item))) context.update(dict(zip(self.loopvars, item)))
else: else:
context[self.loopvars[0]] = item context[self.loopvars[0]] = item
@ -154,8 +154,8 @@ class IfChangedNode(Node):
self._last_seen = None self._last_seen = None
try: try:
if self._varlist: if self._varlist:
# Consider multiple parameters. # Consider multiple parameters. This automatically behaves
# This automatically behaves like a OR evaluation of the multiple variables. # like an OR evaluation of the multiple variables.
compare_to = [var.resolve(context) for var in self._varlist] compare_to = [var.resolve(context) for var in self._varlist]
else: else:
compare_to = self.nodelist.render(context) compare_to = self.nodelist.render(context)
@ -249,13 +249,17 @@ class RegroupNode(Node):
def render(self, context): def render(self, context):
obj_list = self.target.resolve(context, True) obj_list = self.target.resolve(context, True)
if obj_list == None: # target_var wasn't found in context; fail silently if obj_list == None:
# target variable wasn't found in context; fail silently.
context[self.var_name] = [] context[self.var_name] = []
return '' return ''
# List of dictionaries in the format # List of dictionaries in the format:
# {'grouper': 'key', 'list': [list of contents]}. # {'grouper': 'key', 'list': [list of contents]}.
context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in context[self.var_name] = [
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))] {'grouper': key, 'list': list(val)}
for key, val in
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
]
return '' return ''
def include_is_allowed(filepath): def include_is_allowed(filepath):
@ -339,13 +343,15 @@ class URLNode(Node):
def render(self, context): def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args] args = [arg.resolve(context) for arg in self.args]
kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) for k, v in self.kwargs.items()]) kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
for k, v in self.kwargs.items()])
try: try:
return reverse(self.view_name, args=args, kwargs=kwargs) return reverse(self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch: except NoReverseMatch:
try: try:
project_name = settings.SETTINGS_MODULE.split('.')[0] project_name = settings.SETTINGS_MODULE.split('.')[0]
return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs) return reverse(project_name + '.' + self.view_name,
args=args, kwargs=kwargs)
except NoReverseMatch: except NoReverseMatch:
return '' return ''
@ -389,7 +395,7 @@ class WithNode(Node):
#@register.tag #@register.tag
def comment(parser, token): def comment(parser, token):
""" """
Ignore everything between ``{% comment %}`` and ``{% endcomment %}`` Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
""" """
parser.skip_past('endcomment') parser.skip_past('endcomment')
return CommentNode() return CommentNode()
@ -398,7 +404,7 @@ comment = register.tag(comment)
#@register.tag #@register.tag
def cycle(parser, token): def cycle(parser, token):
""" """
Cycle among the given strings each time this tag is encountered Cycles among the given strings each time this tag is encountered.
Within a loop, cycles among the given strings each time through Within a loop, cycles among the given strings each time through
the loop:: the loop::
@ -417,14 +423,14 @@ def cycle(parser, token):
<tr class="{% cycle rowcolors %}">...</tr> <tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, seperated by spaces. Commas can also You can use any number of values, seperated by spaces. Commas can also
be used to separate values; if a comma is used, the cycle values are be used to separate values; if a comma is used, the cycle values are
interpreted as literal strings. interpreted as literal strings.
""" """
# Note: This returns the exact same node on each {% cycle name %} call; that # Note: This returns the exact same node on each {% cycle name %} call;
# is, the node object returned from {% cycle a b c as name %} and the one # that is, the node object returned from {% cycle a b c as name %} and the
# returned from {% cycle name %} are the exact same object. This shouldn't # one returned from {% cycle name %} are the exact same object. This
# cause problems (heh), but if it does, now you know. # shouldn't cause problems (heh), but if it does, now you know.
# #
# Ugly hack warning: this stuffs the named template dict into parser so # Ugly hack warning: this stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using # that names are only unique within each template (as opposed to using
@ -442,10 +448,11 @@ def cycle(parser, token):
args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
if len(args) == 2: if len(args) == 2:
# {% cycle foo %} case # {% cycle foo %} case.
name = args[1] name = args[1]
if not hasattr(parser, '_namedCycleNodes'): if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name) raise TemplateSyntaxError("No named cycles in template."
" '%s' is not defined" % name)
if not name in parser._namedCycleNodes: if not name in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name] return parser._namedCycleNodes[name]
@ -463,7 +470,8 @@ cycle = register.tag(cycle)
def debug(parser, token): def debug(parser, token):
""" """
Output a whole load of debugging information, including the current context and imported modules. Outputs a whole load of debugging information, including the current
context and imported modules.
Sample usage:: Sample usage::
@ -477,7 +485,7 @@ debug = register.tag(debug)
#@register.tag(name="filter") #@register.tag(name="filter")
def do_filter(parser, token): def do_filter(parser, token):
""" """
Filter the contents of the blog through variable filters. Filters the contents of the blog through variable filters.
Filters can also be piped through each other, and they can have Filters can also be piped through each other, and they can have
arguments -- just like in variable syntax. arguments -- just like in variable syntax.
@ -526,14 +534,15 @@ def firstof(parser, token):
""" """
bits = token.split_contents()[1:] bits = token.split_contents()[1:]
if len(bits) < 1: if len(bits) < 1:
raise TemplateSyntaxError, "'firstof' statement requires at least one argument" raise TemplateSyntaxError("'firstof' statement requires at least one"
" argument")
return FirstOfNode(bits) return FirstOfNode(bits)
firstof = register.tag(firstof) firstof = register.tag(firstof)
#@register.tag(name="for") #@register.tag(name="for")
def do_for(parser, token): def do_for(parser, token):
""" """
Loop over each item in an array. Loops over each item in an array.
For example, to display a list of athletes given ``athlete_list``:: For example, to display a list of athletes given ``athlete_list``::
@ -545,9 +554,9 @@ def do_for(parser, token):
You can loop over a list in reverse by using You can loop over a list in reverse by using
``{% for obj in list reversed %}``. ``{% for obj in list reversed %}``.
You can also unpack multiple values from a two-dimensional array:: You can also unpack multiple values from a two-dimensional array::
{% for key,value in dict.items %} {% for key,value in dict.items %}
{{ key }}: {{ value }} {{ key }}: {{ value }}
{% endfor %} {% endfor %}
@ -572,17 +581,20 @@ def do_for(parser, token):
""" """
bits = token.contents.split() bits = token.contents.split()
if len(bits) < 4: if len(bits) < 4:
raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents raise TemplateSyntaxError("'for' statements should have at least four"
" words: %s" % token.contents)
reversed = bits[-1] == 'reversed' reversed = bits[-1] == 'reversed'
in_index = reversed and -3 or -2 in_index = reversed and -3 or -2
if bits[in_index] != 'in': if bits[in_index] != 'in':
raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents raise TemplateSyntaxError("'for' statements should use the format"
" 'for x in y': %s" % token.contents)
loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
for var in loopvars: for var in loopvars:
if not var or ' ' in var: if not var or ' ' in var:
raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents raise TemplateSyntaxError("'for' tag received an invalid argument:"
" %s" % token.contents)
sequence = parser.compile_filter(bits[in_index+1]) sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',)) nodelist_loop = parser.parse(('endfor',))
@ -607,7 +619,7 @@ def do_ifequal(parser, token, negate):
#@register.tag #@register.tag
def ifequal(parser, token): def ifequal(parser, token):
""" """
Output the contents of the block if the two arguments equal each other. Outputs the contents of the block if the two arguments equal each other.
Examples:: Examples::
@ -626,7 +638,10 @@ ifequal = register.tag(ifequal)
#@register.tag #@register.tag
def ifnotequal(parser, token): def ifnotequal(parser, token):
"""Output the contents of the block if the two arguments are not equal. See ifequal.""" """
Outputs the contents of the block if the two arguments are not equal.
See ifequal.
"""
return do_ifequal(parser, token, True) return do_ifequal(parser, token, True)
ifnotequal = register.tag(ifnotequal) ifnotequal = register.tag(ifnotequal)
@ -635,9 +650,7 @@ def do_if(parser, token):
""" """
The ``{% if %}`` tag evaluates a variable, and if that variable is "true" The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
(i.e. exists, is not empty, and is not a false boolean value) the contents (i.e. exists, is not empty, and is not a false boolean value) the contents
of the block are output: of the block are output::
::
{% if athlete_list %} {% if athlete_list %}
Number of athletes: {{ athlete_list|count }} Number of athletes: {{ athlete_list|count }}
@ -648,8 +661,8 @@ def do_if(parser, token):
In the above, if ``athlete_list`` is not empty, the number of athletes will In the above, if ``athlete_list`` is not empty, the number of athletes will
be displayed by the ``{{ athlete_list|count }}`` variable. be displayed by the ``{{ athlete_list|count }}`` 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 option ``{% else %}`` clause
will be displayed if the test fails. that will be displayed if the test fails.
``if`` tags may use ``or``, ``and`` or ``not`` to test a number of ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
variables or to negate a given variable:: variables or to negate a given variable::
@ -674,9 +687,9 @@ def do_if(parser, token):
There are some athletes and absolutely no coaches. There are some athletes and absolutely no coaches.
{% endif %} {% endif %}
``if`` tags do not allow ``and`` and ``or`` clauses with the same ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
tag, because the order of logic would be ambigous. For example, because the order of logic would be ambigous. For example, this is
this is invalid:: invalid::
{% if athlete_list and coach_list or cheerleader_list %} {% if athlete_list and coach_list or cheerleader_list %}
@ -692,8 +705,8 @@ def do_if(parser, token):
bits = token.contents.split() bits = token.contents.split()
del bits[0] del bits[0]
if not bits: if not bits:
raise TemplateSyntaxError, "'if' statement requires at least one argument" raise TemplateSyntaxError("'if' statement requires at least one argument")
# bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
bitstr = ' '.join(bits) bitstr = ' '.join(bits)
boolpairs = bitstr.split(' and ') boolpairs = bitstr.split(' and ')
boolvars = [] boolvars = []
@ -728,13 +741,13 @@ do_if = register.tag("if", do_if)
#@register.tag #@register.tag
def ifchanged(parser, token): def ifchanged(parser, token):
""" """
Check if a value has changed from the last iteration of a loop. Checks if a value has changed from the last iteration of a loop.
The 'ifchanged' block tag is used within a loop. It has two possible uses. The 'ifchanged' block tag is used within a loop. It has two possible uses.
1. Checks its own rendered contents against its previous state and only 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 displays the content if it has changed. For example, this displays a
days, only displaying the month if it changes:: list of days, only displaying the month if it changes::
<h1>Archive for {{ year }}</h1> <h1>Archive for {{ year }}</h1>
@ -743,9 +756,9 @@ def ifchanged(parser, token):
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
{% endfor %} {% endfor %}
2. If given a variable, check whether that variable has changed. For example, the 2. If given a variable, check whether that variable has changed.
following shows the date every time it changes, but only shows the hour if both For example, the following shows the date every time it changes, but
the hour and the date have changed:: only shows the hour if both the hour and the date have changed::
{% for date in days %} {% for date in days %}
{% ifchanged date.date %} {{ date.date }} {% endifchanged %} {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
@ -763,7 +776,7 @@ ifchanged = register.tag(ifchanged)
#@register.tag #@register.tag
def ssi(parser, token): def ssi(parser, token):
""" """
Output the contents of a given file into the page. Outputs the contents of a given file into the page.
Like a simple "include" tag, the ``ssi`` tag includes the contents Like a simple "include" tag, the ``ssi`` tag includes the contents
of another file -- which must be specified using an absolute page -- of another file -- which must be specified using an absolute page --
@ -779,21 +792,24 @@ def ssi(parser, token):
bits = token.contents.split() bits = token.contents.split()
parsed = False parsed = False
if len(bits) not in (2, 3): if len(bits) not in (2, 3):
raise TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included" raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
" the file to be included")
if len(bits) == 3: if len(bits) == 3:
if bits[2] == 'parsed': if bits[2] == 'parsed':
parsed = True parsed = True
else: else:
raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0] raise TemplateSyntaxError("Second (optional) argument to %s tag"
" must be 'parsed'" % bits[0])
return SsiNode(bits[1], parsed) return SsiNode(bits[1], parsed)
ssi = register.tag(ssi) ssi = register.tag(ssi)
#@register.tag #@register.tag
def load(parser, token): def load(parser, token):
""" """
Load a custom template tag set. Loads a custom template tag set.
For example, to load the template tags in ``django/templatetags/news/photos.py``:: For example, to load the template tags in
``django/templatetags/news/photos.py``::
{% load news.photos %} {% load news.photos %}
""" """
@ -804,14 +820,15 @@ def load(parser, token):
lib = get_library("django.templatetags.%s" % taglib) lib = get_library("django.templatetags.%s" % taglib)
parser.add_library(lib) parser.add_library(lib)
except InvalidTemplateLibrary, e: except InvalidTemplateLibrary, e:
raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e) raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
(taglib, e))
return LoadNode() return LoadNode()
load = register.tag(load) load = register.tag(load)
#@register.tag #@register.tag
def now(parser, token): def now(parser, token):
""" """
Display the date, formatted according to the given string. Displays the date, formatted according to the given string.
Uses the same format as PHP's ``date()`` function; see http://php.net/date Uses the same format as PHP's ``date()`` function; see http://php.net/date
for all the possible values. for all the possible values.
@ -830,7 +847,7 @@ now = register.tag(now)
#@register.tag #@register.tag
def regroup(parser, token): def regroup(parser, token):
""" """
Regroup a list of alike objects by a common attribute. Regroups a list of alike objects by a common attribute.
This complex tag is best illustrated by use of an example: say that This complex tag is best illustrated by use of an example: say that
``people`` is a list of ``Person`` objects that have ``first_name``, ``people`` is a list of ``Person`` objects that have ``first_name``,
@ -868,8 +885,8 @@ def regroup(parser, token):
Note that `{% regroup %}`` does not work when the list to be grouped is not Note that `{% regroup %}`` does not work when the list to be grouped is not
sorted by the key you are grouping by! This means that if your list of sorted by the key you are grouping by! This means that if your list of
people was not sorted by gender, you'd need to make sure it is sorted before people was not sorted by gender, you'd need to make sure it is sorted
using it, i.e.:: before using it, i.e.::
{% regroup people|dictsort:"gender" by gender as grouped %} {% regroup people|dictsort:"gender" by gender as grouped %}
@ -879,10 +896,11 @@ def regroup(parser, token):
raise TemplateSyntaxError, "'regroup' tag takes five arguments" raise TemplateSyntaxError, "'regroup' tag takes five arguments"
target = parser.compile_filter(firstbits[1]) target = parser.compile_filter(firstbits[1])
if firstbits[2] != 'by': if firstbits[2] != 'by':
raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'" raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
lastbits_reversed = firstbits[3][::-1].split(None, 2) lastbits_reversed = firstbits[3][::-1].split(None, 2)
if lastbits_reversed[1][::-1] != 'as': if lastbits_reversed[1][::-1] != 'as':
raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'" raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
" be 'as'")
expression = parser.compile_filter(lastbits_reversed[2][::-1]) expression = parser.compile_filter(lastbits_reversed[2][::-1])
@ -892,8 +910,7 @@ regroup = register.tag(regroup)
def spaceless(parser, token): def spaceless(parser, token):
""" """
Removes whitespace between HTML tags. This includes tab Removes whitespace between HTML tags, including tab and newline characters.
characters and newlines.
Example usage:: Example usage::
@ -907,8 +924,8 @@ def spaceless(parser, token):
<p><a href="foo/">Foo</a></p> <p><a href="foo/">Foo</a></p>
Only space between *tags* is normalized -- not space between tags and text. In Only space between *tags* is normalized -- not space between tags and text.
this example, the space around ``Hello`` won't be stripped:: In this example, the space around ``Hello`` won't be stripped::
{% spaceless %} {% spaceless %}
<strong> <strong>
@ -924,7 +941,7 @@ spaceless = register.tag(spaceless)
#@register.tag #@register.tag
def templatetag(parser, token): def templatetag(parser, token):
""" """
Output one of the bits used to compose template tags. Outputs one of the bits used to compose template tags.
Since the template system has no concept of "escaping", to display one of Since the template system has no concept of "escaping", to display one of
the bits used in template tags, you must use the ``{% templatetag %}`` tag. the bits used in template tags, you must use the ``{% templatetag %}`` tag.
@ -949,8 +966,9 @@ def templatetag(parser, token):
raise TemplateSyntaxError, "'templatetag' statement takes one argument" raise TemplateSyntaxError, "'templatetag' statement takes one argument"
tag = bits[1] tag = bits[1]
if tag not in TemplateTagNode.mapping: if tag not in TemplateTagNode.mapping:
raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \ raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
(tag, TemplateTagNode.mapping.keys()) " Must be one of: %s" %
(tag, TemplateTagNode.mapping.keys()))
return TemplateTagNode(tag) return TemplateTagNode(tag)
templatetag = register.tag(templatetag) templatetag = register.tag(templatetag)
@ -958,7 +976,8 @@ def url(parser, token):
""" """
Returns an absolute URL matching given view with its parameters. Returns an absolute URL matching given view with its parameters.
This is a way to define links that aren't tied to a particular URL configuration:: This is a way to define links that aren't tied to a particular URL
configuration::
{% url path.to.some_view arg1,arg2,name1=value1 %} {% url path.to.some_view arg1,arg2,name1=value1 %}
@ -986,7 +1005,8 @@ def url(parser, token):
""" """
bits = token.contents.split(' ', 2) bits = token.contents.split(' ', 2)
if len(bits) < 2: if len(bits) < 2:
raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0] raise TemplateSyntaxError("'%s' takes at least one argument"
" (path to a view)" % bits[0])
args = [] args = []
kwargs = {} kwargs = {}
if len(bits) > 2: if len(bits) > 2:
@ -1011,8 +1031,8 @@ def widthratio(parser, token):
<img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' /> <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
the above example will be 88 pixels wide (because 175/200 = .875; .875 * the above example will be 88 pixels wide (because 175/200 = .875;
100 = 87.5 which is rounded up to 88). .875 * 100 = 87.5 which is rounded up to 88).
""" """
bits = token.contents.split() bits = token.contents.split()
if len(bits) != 4: if len(bits) != 4:
@ -1029,7 +1049,7 @@ widthratio = register.tag(widthratio)
#@register.tag #@register.tag
def do_with(parser, token): def do_with(parser, token):
""" """
Add a value to the context (inside of this block) for caching and easy Adds a value to the context (inside of this block) for caching and easy
access. access.
For example:: For example::
@ -1040,7 +1060,8 @@ def do_with(parser, token):
""" """
bits = list(token.split_contents()) bits = list(token.split_contents())
if len(bits) != 4 or bits[2] != "as": if len(bits) != 4 or bits[2] != "as":
raise TemplateSyntaxError, "%r expected format is 'value as name'" % bits[0] raise TemplateSyntaxError("%r expected format is 'value as name'" %
bits[0])
var = parser.compile_filter(bits[1]) var = parser.compile_filter(bits[1])
name = bits[3] name = bits[3]
nodelist = parser.parse(('endwith',)) nodelist = parser.parse(('endwith',))

View File

@ -146,7 +146,7 @@ class TestCase(unittest.TestCase):
" context %d does not contain the" " context %d does not contain the"
" error '%s' (actual errors: %s)" % " error '%s' (actual errors: %s)" %
(field, form, i, err, (field, form, i, err,
list(field_errors))) repr(field_errors)))
elif field in context[form].fields: elif field in context[form].fields:
self.fail("The field '%s' on form '%s' in context %d" self.fail("The field '%s' on form '%s' in context %d"
" contains no errors" % (field, form, i)) " contains no errors" % (field, form, i))

View File

@ -13,17 +13,18 @@ into account when building its cache key. Requests with the same path but
different header content for headers named in "Vary" need to get different different header content for headers named in "Vary" need to get different
cache keys to prevent delivery of wrong content. cache keys to prevent delivery of wrong content.
A example: i18n middleware would need to distinguish caches by the An example: i18n middleware would need to distinguish caches by the
"Accept-language" header. "Accept-language" header.
""" """
import md5 import md5
import re import re
import time import time
from email.Utils import formatdate
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.utils.encoding import smart_str, iri_to_uri from django.utils.encoding import smart_str, iri_to_uri
from django.utils.http import http_date
cc_delim_re = re.compile(r'\s*,\s*') cc_delim_re = re.compile(r'\s*,\s*')
@ -40,7 +41,7 @@ def patch_cache_control(response, **kwargs):
str() to it. str() to it.
""" """
def dictitem(s): def dictitem(s):
t = s.split('=',1) t = s.split('=', 1)
if len(t) > 1: if len(t) > 1:
return (t[0].lower(), t[1]) return (t[0].lower(), t[1])
else: else:
@ -64,7 +65,7 @@ def patch_cache_control(response, **kwargs):
if 'max-age' in cc and 'max_age' in kwargs: if 'max-age' in cc and 'max_age' in kwargs:
kwargs['max_age'] = min(cc['max-age'], kwargs['max_age']) kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
for (k,v) in kwargs.items(): for (k, v) in kwargs.items():
cc[k.replace('_', '-')] = v cc[k.replace('_', '-')] = v
cc = ', '.join([dictvalue(el) for el in cc.items()]) cc = ', '.join([dictvalue(el) for el in cc.items()])
response['Cache-Control'] = cc response['Cache-Control'] = cc
@ -88,15 +89,14 @@ def patch_response_headers(response, cache_timeout=None):
if not response.has_header('ETag'): if not response.has_header('ETag'):
response['ETag'] = md5.new(response.content).hexdigest() response['ETag'] = md5.new(response.content).hexdigest()
if not response.has_header('Last-Modified'): if not response.has_header('Last-Modified'):
response['Last-Modified'] = formatdate()[:26] + "GMT" response['Last-Modified'] = http_date()
if not response.has_header('Expires'): if not response.has_header('Expires'):
response['Expires'] = formatdate(time.time() + cache_timeout)[:26] + "GMT" response['Expires'] = http_date(time.time() + cache_timeout)
patch_cache_control(response, max_age=cache_timeout) patch_cache_control(response, max_age=cache_timeout)
def add_never_cache_headers(response): def add_never_cache_headers(response):
""" """
Add headers to a response to indicate that Adds headers to a response to indicate that a page should never be cached.
a page should never be cached.
""" """
patch_response_headers(response, cache_timeout=-1) patch_response_headers(response, cache_timeout=-1)
@ -119,13 +119,14 @@ def patch_vary_headers(response, newheaders):
response['Vary'] = ', '.join(vary) response['Vary'] = ', '.join(vary)
def _generate_cache_key(request, headerlist, key_prefix): def _generate_cache_key(request, headerlist, key_prefix):
"Returns a cache key from the headers given in the header list." """Returns a cache key from the headers given in the header list."""
ctx = md5.new() ctx = md5.new()
for header in headerlist: for header in headerlist:
value = request.META.get(header, None) value = request.META.get(header, None)
if value is not None: if value is not None:
ctx.update(value) ctx.update(value)
return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, iri_to_uri(request.path), ctx.hexdigest()) return 'views.decorators.cache.cache_page.%s.%s.%s' % (
key_prefix, iri_to_uri(request.path), ctx.hexdigest())
def get_cache_key(request, key_prefix=None): def get_cache_key(request, key_prefix=None):
""" """
@ -139,7 +140,8 @@ def get_cache_key(request, key_prefix=None):
""" """
if key_prefix is None: if key_prefix is None:
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, iri_to_uri(request.path))
headerlist = cache.get(cache_key, None) headerlist = cache.get(cache_key, None)
if headerlist is not None: if headerlist is not None:
return _generate_cache_key(request, headerlist, key_prefix) return _generate_cache_key(request, headerlist, key_prefix)
@ -163,9 +165,11 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None):
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
if cache_timeout is None: if cache_timeout is None:
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, iri_to_uri(request.path))
if response.has_header('Vary'): if response.has_header('Vary'):
headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])] headerlist = ['HTTP_'+header.upper().replace('-', '_')
for header in vary_delim_re.split(response['Vary'])]
cache.set(cache_key, headerlist, cache_timeout) cache.set(cache_key, headerlist, cache_timeout)
return _generate_cache_key(request, headerlist, key_prefix) return _generate_cache_key(request, headerlist, key_prefix)
else: else:

View File

@ -170,7 +170,7 @@ class DateFormat(TimeFormat):
return u"%+03d%02d" % (seconds // 3600, (seconds // 60) % 60) return u"%+03d%02d" % (seconds // 3600, (seconds // 60) % 60)
def r(self): def r(self):
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'" "RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
return self.format('D, j M Y H:i:s O') return self.format('D, j M Y H:i:s O')
def S(self): def S(self):

View File

@ -1,8 +1,19 @@
import types import types
import urllib import urllib
import datetime import datetime
from django.utils.functional import Promise from django.utils.functional import Promise
class DjangoUnicodeDecodeError(UnicodeDecodeError):
def __init__(self, obj, *args):
self.obj = obj
UnicodeDecodeError.__init__(self, *args)
def __str__(self):
original = UnicodeDecodeError.__str__(self)
return '%s. You passed in %r (%s)' % (original, self.obj,
type(self.obj))
class StrAndUnicode(object): class StrAndUnicode(object):
""" """
A class whose __str__ returns its __unicode__ as a UTF-8 bytestring. A class whose __str__ returns its __unicode__ as a UTF-8 bytestring.
@ -33,13 +44,16 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
""" """
if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)): if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)):
return s return s
if not isinstance(s, basestring,): try:
if hasattr(s, '__unicode__'): if not isinstance(s, basestring,):
s = unicode(s) if hasattr(s, '__unicode__'):
else: s = unicode(s)
s = unicode(str(s), encoding, errors) else:
elif not isinstance(s, unicode): s = unicode(str(s), encoding, errors)
s = unicode(s, encoding, errors) elif not isinstance(s, unicode):
s = unicode(s, encoding, errors)
except UnicodeDecodeError, e:
raise DjangoUnicodeDecodeError(s, *e.args)
return s return s
def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):

View File

@ -1,4 +1,6 @@
import urllib import urllib
from email.Utils import formatdate
from django.utils.encoding import smart_str, force_unicode from django.utils.encoding import smart_str, force_unicode
from django.utils.functional import allow_lazy from django.utils.functional import allow_lazy
@ -37,3 +39,29 @@ def urlencode(query, doseq=0):
for k, v in query], for k, v in query],
doseq) doseq)
def cookie_date(epoch_seconds=None):
"""
Formats the time to ensure compatibility with Netscape's cookie standard.
Accepts a floating point number expressed in seconds since the epoch, in
UTC - such as that outputted by time.time(). If set to None, defaults to
the current time.
Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'.
"""
rfcdate = formatdate(epoch_seconds)
return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25])
def http_date(epoch_seconds=None):
"""
Formats the time to match the RFC1123 date format as specified by HTTP
RFC2616 section 3.3.1.
Accepts a floating point number expressed in seconds since the epoch, in
UTC - such as that outputted by time.time(). If set to None, defaults to
the current time.
Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
"""
rfcdate = formatdate(epoch_seconds)
return '%s GMT' % rfcdate[:25]

View File

@ -7,13 +7,14 @@ import mimetypes
import os import os
import posixpath import posixpath
import re import re
import rfc822
import stat import stat
import urllib import urllib
from email.Utils import parsedate_tz, mktime_tz
from django.template import loader from django.template import loader
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
from django.template import Template, Context, TemplateDoesNotExist from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date
def serve(request, path, document_root=None, show_indexes=False): def serve(request, path, document_root=None, show_indexes=False):
""" """
@ -60,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False):
mimetype = mimetypes.guess_type(fullpath)[0] mimetype = mimetypes.guess_type(fullpath)[0]
contents = open(fullpath, 'rb').read() contents = open(fullpath, 'rb').read()
response = HttpResponse(contents, mimetype=mimetype) response = HttpResponse(contents, mimetype=mimetype)
response["Last-Modified"] = rfc822.formatdate(statobj[stat.ST_MTIME]) response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
return response return response
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
@ -119,8 +120,7 @@ def was_modified_since(header=None, mtime=0, size=0):
raise ValueError raise ValueError
matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
re.IGNORECASE) re.IGNORECASE)
header_mtime = rfc822.mktime_tz(rfc822.parsedate_tz( header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
matches.group(1)))
header_len = matches.group(3) header_len = matches.group(3)
if header_len and int(header_len) != size: if header_len and int(header_len) != size:
raise ValueError raise ValueError

View File

@ -291,13 +291,15 @@ minutes.
Template fragment caching Template fragment caching
========================= =========================
**New in development version**.
If you're after even more control, you can also cache template fragments using If you're after even more control, you can also cache template fragments using
the ``cache`` template tag. To give your template access to this tag, put ``{% the ``cache`` template tag. To give your template access to this tag, put
load cache %}`` near the top of your template. ``{% load cache %}`` near the top of your template.
The ``{% cache %}`` template tag caches the contents of the block for a given The ``{% cache %}`` template tag caches the contents of the block for a given
amount of time. It takes at least two arguments: the cache timeout, in amount of time. It takes at least two arguments: the cache timeout, in seconds,
seconds, and the name to give the cache fragment. For example:: and the name to give the cache fragment. For example::
{% load cache %} {% load cache %}
{% cache 500 sidebar %} {% cache 500 sidebar %}

View File

@ -0,0 +1,567 @@
===================
Custom Model Fields
===================
**New in Django development version**
Introduction
============
The `model reference`_ documentation explains how to use Django's standard
field classes. For many purposes, those classes are all you'll need. Sometimes,
though, the Django version won't meet your precise requirements, or you'll want
to use a field that is entirely different from those shipped with Django.
Django's built-in field types don't cover every possible database column type --
only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure
column types, such as geographic polygons or even user-created types such as
`PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses.
Alternatively, you may have a complex Python object that can somehow be
serialized to fit into a standard database column type. This is another case
where a ``Field`` subclass will help you use your object with your models.
Our example object
------------------
Creating custom fields requires a bit of attention to detail. To make things
easier to follow, we'll use a consistent example throughout this document.
Suppose you have a Python object representing the deal of cards in a hand of
Bridge_. It doesn't matter if you don't know how to play Bridge. You only need
to know that 52 cards are dealt out equally to four players, who are
traditionally called *north*, *east*, *south* and *west*. Our class looks
something like this::
class Hand(object):
def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc)
self.north = north
self.east = east
self.south = south
self.west = west
# ... (other possibly useful methods omitted) ...
This is just an ordinary Python class, nothing Django-specific about it. We
would like to be able to things like this in our models (we assume the
``hand`` attribute on the model is an instance of ``Hand``)::
example = MyModel.objects.get(pk=1)
print example.hand.north
new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()
We assign to and retrieve from the ``hand`` attribute in our model just like
any other Python class. The trick is to tell Django how to handle saving and
loading such an object
In order to use the ``Hand`` class in our models, we **do not** have to change
this class at all. This is ideal, because it means you can easily write
model support for existing classes where you cannot change the source code.
.. note::
You might only be wanting to take advantage of custom database column
types and deal with the data as standard Python types in your models;
strings, or floats, for example. This case is similar to our ``Hand``
example and we'll note any differences as we go along.
.. _model reference: ../model_api/
.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html
.. _Bridge: http://en.wikipedia.org/wiki/Contract_bridge
Background Theory
=================
Database storage
----------------
The simplest way to think of a model field is that it provides a way to take a
normal Python object -- string, boolean, ``datetime``, or something more
complex like ``Hand`` -- and convert it to and from a format that is useful
when dealing with the database (and serialization, but, as we'll see later,
that falls out fairly naturally once you have the database side under control).
Fields in a model must somehow be converted to fit into an existing database
column type. Different databases provide different sets of valid column types,
but the rule is still the same: those are the only types you have to work
with. Anything you want to store in the database must fit into one of
those types.
Normally, you're either writing a Django field to match a particular database
column type, or there's a fairly straightforward way to convert your data to,
say, a string.
For our ``Hand`` example, we could convert the card data to a string of 104
characters by concatenating all the cards together in a pre-determined order.
Say, all the *north* cards first, then the *east*, *south* and *west* cards, in
that order. So ``Hand`` objects can be saved to text or character columns in
the database
What does a field class do?
---------------------------
All of Django's fields (and when we say *fields* in this document, we always
mean model fields and not `form fields`_) are subclasses of
``django.db.models.Field``. Most of the information that Django records about a
field is common to all fields -- name, help text, validator lists, uniqueness
and so forth. Storing all that information is handled by ``Field``. We'll get
into the precise details of what ``Field`` can do later on; for now, suffice it
to say that everything descends from ``Field`` and then customises key pieces
of the class behaviour.
.. _form fields: ../newforms/#fields
It's important to realise that a Django field class is not what is stored in
your model attributes. The model attributes contain normal Python objects. The
field classes you define in a model are actually stored in the ``Meta`` class
when the model class is created (the precise details of how this is done are
unimportant here). This is because the field classes aren't necessary when
you're just creating and modifying attributes. Instead, they provide the
machinery for converting between the attribute value and what is stored in the
database or sent to the serializer.
Keep this in mind when creating your own custom fields. The Django ``Field``
subclass you write provides the machinery for converting between your Python
instances and the database/serializer values in various ways (there are
differences between storing a value and using a value for lookups, for
example). If this sounds a bit tricky, don't worry. It will hopefully become
clearer in the examples below. Just remember that you will often end up
creating two classes when you want a custom field. The first class is the
Python object that your users will manipulate. They will assign it to the model
attribute, they will read from it for displaying purposes, things like that.
This is the ``Hand`` class in our example. The second class is the ``Field``
subclass. This is the class that knows how to convert your first class back and
forth between its permanent storage form and the Python form.
Writing a ``Field`` subclass
=============================
When you are planning your ``Field`` subclass, first give some thought to
which existing field your new field is most similar to. Can you subclass an
existing Django field and save yourself some work? If not, you should subclass the ``Field`` class, from which everything is descended.
Initialising your new field is a matter of separating out any arguments that
are specific to your case from the common arguments and passing the latter to
the ``__init__()`` method of ``Field`` (or your parent class).
In our example, the Django field we create is going to be called
``HandField``. It's not a bad idea to use a similar naming scheme to Django's
fields so that our new class is identifiable and yet clearly related to the
``Hand`` class it is wrapping. It doesn't behave like any existing field, so
we'll subclass directly from ``Field``::
from django.db import models
class HandField(models.Field):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
Our ``HandField`` will accept most of the standard field options (see the list
below), but we ensure it has a fixed length, since it only needs to hold 52
card values plus their suits; 104 characters in total.
.. note::
Many of Django's model fields accept options that they don't do anything
with. For example, you can pass both ``editable`` and ``auto_now`` to a
``DateField`` and it will simply ignore the ``editable`` parameter
(``auto_now`` being set implies ``editable=False``). No error is raised in
this case.
This behaviour simplifies the field classes, because they don't need to
check for options that aren't necessary. They just pass all the options to
the parent class and then don't use them later on. It is up to you whether
you want your fields to be more strict about the options they select, or
to use the simpler, more permissive behaviour of the current fields.
The ``Field.__init__()`` method takes the following parameters, in this
order:
- ``verbose_name``
- ``name``
- ``primary_key``
- ``max_length``
- ``unique``
- ``blank``
- ``null``
- ``db_index``
- ``core``
- ``rel``: Used for related fields (like ``ForeignKey``). For advanced use
only.
- ``default``
- ``editable``
- ``serialize``: If ``False``, the field will not be serialized when the
model is passed to Django's serializers_. Defaults to ``True``.
- ``prepopulate_from``
- ``unique_for_date``
- ``unique_for_month``
- ``unique_for_year``
- ``validator_list``
- ``choices``
- ``radio_admin``
- ``help_text``
- ``db_column``
- ``db_tablespace``: Currently only used with the Oracle backend and only
for index creation. You can usually ignore this option.
All of the options without an explanation in the above list have the same
meaning they do for normal Django fields. See the `model documentation`_ for
examples and details.
.. _serializers: ../serialization/
.. _model documentation: ../model-api/
The ``SubfieldBase`` metaclass
------------------------------
As we indicated in the introduction_, field subclasses are often needed for
two reasons. Either to take advantage of a custom database column type, or to
handle complex Python types. A combination of the two is obviously also
possible. If you are only working with custom database column types and your
model fields appear in Python as standard Python types direct from the
database backend, you don't need to worry about this section.
If you are handling custom Python types, such as our ``Hand`` class, we need
to make sure that when Django initialises an instance of our model and assigns
a database value to our custom field attribute we convert that value into the
appropriate Python object. The details of how this happens internally are a
little complex. For the field writer, though, things are fairly simple. Make
sure your field subclass uses ``django.db.models.SubfieldBase`` as its
metaclass. This ensures that the ``to_python()`` method, documented below_,
will always be called when the attribute is initialised.
Our ``HandleField`` class now looks like this::
class HandleField(models.Field):
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
# ...
.. _below: #to-python-self-value
Useful methods
--------------
Once you've created your ``Field`` subclass and setup up the
``__metaclass__``, if necessary, there are a few standard methods you need to
consider overriding. Which of these you need to implement will depend on you
particular field behaviour. The list below is in approximately decreasing
order of importance, so start from the top.
``db_type(self)``
~~~~~~~~~~~~~~~~~
Returns the database column data type for the ``Field``, taking into account
the current ``DATABASE_ENGINE`` setting.
Say you've created a PostgreSQL custom type called ``mytype``. You can use this
field with Django by subclassing ``Field`` and implementing the ``db_type()``
method, like so::
from django.db import models
class MytypeField(models.Field):
def db_type(self):
return 'mytype'
Once you have ``MytypeField``, you can use it in any model, just like any other
``Field`` type::
class Person(models.Model):
name = models.CharField(max_length=80)
gender = models.CharField(max_length=1)
something_else = MytypeField()
If you aim to build a database-agnostic application, you should account for
differences in database column types. For example, the date/time column type
in PostgreSQL is called ``timestamp``, while the same column in MySQL is called
``datetime``. The simplest way to handle this in a ``db_type()`` method is to
import the Django settings module and check the ``DATABASE_ENGINE`` setting.
For example::
class MyDateField(models.Field):
def db_type(self):
from django.conf import settings
if settings.DATABASE_ENGINE == 'mysql':
return 'datetime'
else:
return 'timestamp'
The ``db_type()`` method is only called by Django when the framework constructs
the ``CREATE TABLE`` statements for your application -- that is, when you first
create your tables. It's not called at any other time, so it can afford to
execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the
above example.
Some database column types accept parameters, such as ``CHAR(25)``, where the
parameter ``25`` represents the maximum column length. In cases like these,
it's more flexible if the parameter is specified in the model rather than being
hard-coded in the ``db_type()`` method. For example, it wouldn't make much
sense to have a ``CharMaxlength25Field``, shown here::
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self):
return 'char(25)'
# In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
The better way of doing this would be to make the parameter specifiable at run
time -- i.e., when the class is instantiated. To do that, just implement
``__init__()``, like so::
# This is a much more flexible example.
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(BetterCharField, self).__init__(*args, **kwargs)
def db_type(self):
return 'char(%s)' % self.max_length
# In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
Finally, if your column requires truly complex SQL setup, return ``None`` from
``db_type()``. This will cause Django's SQL creation code to skip over this
field. You are then responsible for creating the column in the right table in
some other way, of course, but this gives you a way to tell Django to get out
of the way.
``to_python(self, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~
Converts between all the ways your field can receive its initial value and the
Python object you want to end up with. The default version just returns
``value``, so is useful is the database backend returns the data already in
the correct form (a Python string, for example).
Normally, you will need to override this method. As a general rule, be
prepared to accept an instance of the right type (e.g. ``Hand`` in our ongoing
example), a string (from a deserializer, for example), and whatever the
database wrapper returns for the column type you are using.
In our ``HandField`` class, we are storing the data in a character field in
the database, so we need to be able to process strings and ``Hand`` instances
in ``to_python()``::
class HandField(models.Field):
# ...
def to_python(self, value):
if isinstance(value, Hand):
return value
# The string case
p1 = re.compile('.{26}')
p2 = re.compile('..')
args = [p2.findall(x) for x in p1.findall(value)]
return Hand(*args)
Notice that we always return a ``Hand`` instance from this method. That is the
Python object we want to store in the model's attribute.
``get_db_prep_save(self, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the reverse of ``to_python()`` when working with the database backends
(as opposed to serialization). The ``value`` parameter is the current value of
the model's attribute (a field has no reference to its containing model, so it
cannot retrieve the value itself) and the method should return data in a
format that can be used as a parameter in a query for the database backend.
For example::
class HandField(models.Field):
# ...
def get_db_prep_save(self, value):
return ''.join([''.join(l) for l in (self.north,
self.east, self.south, self.west)])
``pre_save(self, model_instance, add)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This method is called just prior to ``get_db_prep_save()`` and should return
the value of the appropriate attribute from ``model_instance`` for this field.
The attribute name is in ``self.attname`` (this is set up by ``Field``). If
the model is being saved to the database for the first time, the ``add``
parameter will be ``True``, otherwise it will be ``False``.
Often you won't need to override this method. However, at times it can be very
useful. For example, the Django ``DateTimeField`` uses this method to set the
attribute to the correct value before returning it in the cases when
``auto_now`` or ``auto_now_add`` are set on the field.
If you do override this method, you must return the value of the attribute at
the end. You should also update the model's attribute if you make any changes
to the value so that code holding references to the model will always see the
correct value.
``get_db_prep_lookup(self, lookup_type, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Prepares the ``value`` for passing to the database when used in a lookup (a
``WHERE`` constraint in SQL). The ``lookup_type`` will be one of the valid
Django filter lookups: ``exact``, ``iexact``, ``contains``, ``icontains``,
``gt``, ``gte``, ``lt``, ``lte``, ``in``, ``startswith``, ``istartswith``,
``endswith``, ``iendswith``, ``range``, ``year``, ``month``, ``day``,
``isnull``, ``search``, ``regex``, and ``iregex``.
Your method must be prepared to handle all of these ``lookup_type`` values and
should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a
list when you were expecting an object, for example) or a ``TypeError`` if
your field does not support that type of lookup. For many fields, you can get
by with handling the lookup types that need special handling for your field
and pass the rest of the ``get_db_prep_lookup()`` method of the parent class.
If you needed to implement ``get_db_prep_save()``, you will usually need to
implement ``get_db_prep_lookup()``. The usual reason is because of the
``range`` and ``in`` lookups. In these case, you will passed a list of
objects (presumably of the right type) and will need to convert them to a list
of things of the right type for passing to the database. Sometimes you can
reuse ``get_db_prep_save()``, or at least factor out some common pieces from
both methods into a help function.
For example::
class HandField(models.Field):
# ...
def get_db_prep_lookup(self, lookup_type, value):
# We only handle 'exact' and 'in'. All others are errors.
if lookup_type == 'exact':
return self.get_db_prep_save(value)
elif lookup_type == 'in':
return [self.get_db_prep_save(v) for v in value]
else:
raise TypeError('Lookup type %r not supported.' % lookup_type)
``formfield(self, form_class=forms.CharField, **kwargs)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns the default form field to use when this field is displayed
in a model. This method is called by the `helper functions`_
``form_for_model()`` and ``form_for_instance()``.
All of the ``kwargs`` dictionary is passed directly to the form field's
``__init__()`` method. Normally, all you need to do is set up a good default
for the ``form_class`` argument and then delegate further handling to the
parent class. This might require you to write a custom form field (and even a
form widget). See the `forms documentation`_ for information about this. Also
have a look at ``django.contrib.localflavor`` for some examples of custom
widgets.
Continuing our ongoing example, we can write the ``formfield()`` method as::
class HandField(models.Field):
# ...
def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults
# whilst letting the caller override them.
defaults = {'form_class': MyFormField}
defaults.update(kwargs)
return super(HandField, self).formfield(**defaults)
This assumes we have some ``MyFormField`` field class (which has its own
default widget) imported. This document doesn't cover the details of writing
custom form fields.
.. _helper functions: ../newforms/#generating-forms-for-models
.. _forms documentation: ../newforms/
``get_internal_type(self)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a string giving the name of the ``Field`` subclass we are emulating at
the database level. This is used to determine the type of database column for
simple cases.
If you have created a ``db_type()`` method, you do not need to worry about
``get_internal_type()`` -- it won't be used much. Sometimes, though, your
database storage is similar in type to some other field, so you can use that
other field's logic to create the right column.
For example::
class HandField(models.Field):
# ...
def get_internal_type(self):
return 'CharField'
No matter which database backend we are using, this will mean that ``syncdb``
and other SQL commands create the right column type for storing a string.
If ``get_internal_type()`` returns a string that is not known to Django for
the database backend you are using -- that is, it doesn't appear in
``django.db.backends.<db_name>.creation.DATA_TYPES`` -- the string will still
be used by the serializer, but the default ``db_type()`` method will return
``None``. See the documentation of ``db_type()`` above_ for reasons why this
might be useful. Putting a descriptive string in as the type of the field for
the serializer is a useful idea if you are ever going to be using the
serializer output in some other place, outside of Django.
.. _above: #db-type-self
``flatten_data(self, follow, obj=None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. admonition:: Subject to change
Although implementing this method is necessary to allow field
serialization, the API might change in the future.
Returns a dictionary, mapping the field's attribute name to a flattened string
version of the data. This method has some internal uses that aren't of
interest to use here (mostly having to do with manipulators). For our
purposes, it is sufficient to return a one item dictionary that maps the
attribute name to a string.
This method is used by the serializers to convert the field into a string for
output. You can ignore the input parameters for serialization purposes,
although calling ``Field._get_val_from_obj(obj)`` is the best way to get the
value to serialize.
For example, since our ``HandField`` uses strings for its data storage anyway,
we can reuse some existing conversion code::
class HandField(models.Field):
# ...
def flatten_data(self, follow, obj=None):
value = self._get_val_from_obj(obj)
return {self.attname: self.get_db_prep_save(value)}
Some general advice
--------------------
Writing a custom field can be a tricky process sometime, particularly if you
are doing complex conversions between your Python types and your database and
serialization formats. A couple of tips to make things go more smoothly:
1. Look at the existing Django fields (in
``django/db/models/fields/__init__.py``) for inspiration. Try to find a field
that is already close to what you want and extend it a little bit, in
preference to creating an entirely new field from scratch.
2. Put a ``__str__()`` or ``__unicode__()`` method on the class you are
wrapping up as a field. There are a lot of places where the default behaviour
of the field code is to call ``force_unicode()`` on the value (in our
examples in this document, ``value`` would be a ``Hand`` instance, not a
``HandField``). So if your ``__unicode__()`` method automatically converts to
the string form of your Python object, you can save yourself a lot of work.

View File

@ -275,7 +275,7 @@ The class has the following methods:
There are two ways to call ``attach()``: There are two ways to call ``attach()``:
* You can pass it a single argument that is an * You can pass it a single argument that is an
``email.MIMBase.MIMEBase`` instance. This will be inserted directly ``email.MIMEBase.MIMEBase`` instance. This will be inserted directly
into the resulting message. into the resulting message.
* Alternatively, you can pass ``attach()`` three arguments: * Alternatively, you can pass ``attach()`` three arguments:

View File

@ -45,7 +45,7 @@ How to use ``FormPreview``
2. Create a ``FormPreview`` subclass that overrides the ``done()`` method:: 2. Create a ``FormPreview`` subclass that overrides the ``done()`` method::
from django.contrib.formtools import FormPreview from django.contrib.formtools.preview import FormPreview
from myapp.models import SomeModel from myapp.models import SomeModel
class SomeModelFormPreview(FormPreview): class SomeModelFormPreview(FormPreview):

View File

@ -1013,111 +1013,12 @@ See the `One-to-one relationship model example`_ for a full example.
Custom field types Custom field types
------------------ ------------------
**New in Django development version** If one of the existing model fields cannot be used to fit your purposes, or if
you wish to take advantage of some less common database column types, you can
create your own field class. Full coverage of creating your own fields is
provided in the `Custom Model Fields`_ documentation.
Django's built-in field types don't cover every possible database column type -- .. _Custom Model Fields: ../custom_model_fields/
only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure
column types, such as geographic polygons or even user-created types such as
`PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses.
.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html
.. admonition:: Experimental territory
This is an area of Django that traditionally has not been documented, but
we're starting to include bits of documentation, one feature at a time.
Please forgive the sparseness of this section.
If you like living on the edge and are comfortable with the risk of
unstable, undocumented APIs, see the code for the core ``Field`` class
in ``django/db/models/fields/__init__.py`` -- but if/when the innards
change, don't say we didn't warn you.
To create a custom field type, simply subclass ``django.db.models.Field``.
Here is an incomplete list of the methods you should implement:
``db_type()``
~~~~~~~~~~~~~
Returns the database column data type for the ``Field``, taking into account
the current ``DATABASE_ENGINE`` setting.
Say you've created a PostgreSQL custom type called ``mytype``. You can use this
field with Django by subclassing ``Field`` and implementing the ``db_type()``
method, like so::
from django.db import models
class MytypeField(models.Field):
def db_type(self):
return 'mytype'
Once you have ``MytypeField``, you can use it in any model, just like any other
``Field`` type::
class Person(models.Model):
name = models.CharField(max_length=80)
gender = models.CharField(max_length=1)
something_else = MytypeField()
If you aim to build a database-agnostic application, you should account for
differences in database column types. For example, the date/time column type
in PostgreSQL is called ``timestamp``, while the same column in MySQL is called
``datetime``. The simplest way to handle this in a ``db_type()`` method is to
import the Django settings module and check the ``DATABASE_ENGINE`` setting.
For example::
class MyDateField(models.Field):
def db_type(self):
from django.conf import settings
if settings.DATABASE_ENGINE == 'mysql':
return 'datetime'
else:
return 'timestamp'
The ``db_type()`` method is only called by Django when the framework constructs
the ``CREATE TABLE`` statements for your application -- that is, when you first
create your tables. It's not called at any other time, so it can afford to
execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the
above example.
Some database column types accept parameters, such as ``CHAR(25)``, where the
parameter ``25`` represents the maximum column length. In cases like these,
it's more flexible if the parameter is specified in the model rather than being
hard-coded in the ``db_type()`` method. For example, it wouldn't make much
sense to have a ``CharMaxlength25Field``, shown here::
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self):
return 'char(25)'
# In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
The better way of doing this would be to make the parameter specifiable at run
time -- i.e., when the class is instantiated. To do that, just implement
``__init__()``, like so::
# This is a much more flexible example.
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(BetterCharField, self).__init__(*args, **kwargs)
def db_type(self):
return 'char(%s)' % self.max_length
# In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
Note that if you implement ``__init__()`` on a ``Field`` subclass, it's
important to call ``Field.__init__()`` -- i.e., the parent class'
``__init__()`` method.
Meta options Meta options
============ ============

View File

@ -150,7 +150,7 @@ mess things up. Use the ``PythonInterpreter`` directive to give different
<Location "/otherthing"> <Location "/otherthing">
SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
PythonInterpreter mysite_other PythonInterpreter othersite
</Location> </Location>
</VirtualHost> </VirtualHost>

View File

@ -1081,6 +1081,30 @@ fields. We've specified ``auto_id=False`` to simplify the output::
<p>Sender: <input type="text" name="sender" /> A valid e-mail address, please.</p> <p>Sender: <input type="text" name="sender" /> A valid e-mail address, please.</p>
<p>Cc myself: <input type="checkbox" name="cc_myself" /></p> <p>Cc myself: <input type="checkbox" name="cc_myself" /></p>
``error_messages``
~~~~~~~~~~~~~~~~~~
**New in Django development version**
The ``error_messages`` argument lets you override the default messages which the
field will raise. Pass in a dictionary with keys matching the error messages you
want to override. For example::
>>> generic = forms.CharField()
>>> generic.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> name = forms.CharField(error_messages={'required': 'Please enter your name'})
>>> name.clean('')
Traceback (most recent call last):
...
ValidationError: [u'Please enter your name']
In the `built-in Field classes`_ section below, each Field defines the error
message keys it uses.
Dynamic initial values Dynamic initial values
---------------------- ----------------------
@ -1146,6 +1170,7 @@ For each field, we describe the default widget used if you don't specify
* Normalizes to: A Python ``True`` or ``False`` value. * Normalizes to: A Python ``True`` or ``False`` value.
* Validates that the check box is checked (i.e. the value is ``True``) if * Validates that the check box is checked (i.e. the value is ``True``) if
the field has ``required=True``. the field has ``required=True``.
* Error message keys: ``required``
**New in Django development version:** The empty value for a ``CheckboxInput`` **New in Django development version:** The empty value for a ``CheckboxInput``
(and hence the standard ``BooleanField``) has changed to return ``False`` (and hence the standard ``BooleanField``) has changed to return ``False``
@ -1165,6 +1190,7 @@ instead of ``None`` in the development version.
* Normalizes to: A Unicode object. * Normalizes to: A Unicode object.
* Validates ``max_length`` or ``min_length``, if they are provided. * Validates ``max_length`` or ``min_length``, if they are provided.
Otherwise, all inputs are valid. Otherwise, all inputs are valid.
* Error message keys: ``required``, ``max_length``, ``min_length``
Has two optional arguments for validation, ``max_length`` and ``min_length``. Has two optional arguments for validation, ``max_length`` and ``min_length``.
If provided, these arguments ensure that the string is at most or at least the If provided, these arguments ensure that the string is at most or at least the
@ -1177,6 +1203,7 @@ given length.
* Empty value: ``''`` (an empty string) * Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object. * Normalizes to: A Unicode object.
* Validates that the given value exists in the list of choices. * Validates that the given value exists in the list of choices.
* Error message keys: ``required``, ``invalid_choice``
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
tuple) of 2-tuples to use as choices for this field. tuple) of 2-tuples to use as choices for this field.
@ -1189,6 +1216,7 @@ tuple) of 2-tuples to use as choices for this field.
* Normalizes to: A Python ``datetime.date`` object. * Normalizes to: A Python ``datetime.date`` object.
* Validates that the given value is either a ``datetime.date``, * Validates that the given value is either a ``datetime.date``,
``datetime.datetime`` or string formatted in a particular date format. ``datetime.datetime`` or string formatted in a particular date format.
* Error message keys: ``required``, ``invalid``
Takes one optional argument, ``input_formats``, which is a list of formats used Takes one optional argument, ``input_formats``, which is a list of formats used
to attempt to convert a string to a valid ``datetime.date`` object. to attempt to convert a string to a valid ``datetime.date`` object.
@ -1209,6 +1237,7 @@ If no ``input_formats`` argument is provided, the default input formats are::
* Normalizes to: A Python ``datetime.datetime`` object. * Normalizes to: A Python ``datetime.datetime`` object.
* Validates that the given value is either a ``datetime.datetime``, * Validates that the given value is either a ``datetime.datetime``,
``datetime.date`` or string formatted in a particular datetime format. ``datetime.date`` or string formatted in a particular datetime format.
* Error message keys: ``required``, ``invalid``
Takes one optional argument, ``input_formats``, which is a list of formats used Takes one optional argument, ``input_formats``, which is a list of formats used
to attempt to convert a string to a valid ``datetime.datetime`` object. to attempt to convert a string to a valid ``datetime.datetime`` object.
@ -1238,6 +1267,9 @@ If no ``input_formats`` argument is provided, the default input formats are::
* Normalizes to: A Python ``decimal``. * Normalizes to: A Python ``decimal``.
* Validates that the given value is a decimal. Leading and trailing * Validates that the given value is a decimal. Leading and trailing
whitespace is ignored. whitespace is ignored.
* Error message keys: ``required``, ``invalid``, ``max_value``,
``min_value``, ``max_digits``, ``max_decimal_places``,
``max_whole_digits``
Takes four optional arguments: ``max_value``, ``min_value``, ``max_digits``, Takes four optional arguments: ``max_value``, ``min_value``, ``max_digits``,
and ``decimal_places``. The first two define the limits for the fields value. and ``decimal_places``. The first two define the limits for the fields value.
@ -1254,6 +1286,7 @@ decimal places permitted.
* Normalizes to: A Unicode object. * Normalizes to: A Unicode object.
* Validates that the given value is a valid e-mail address, using a * Validates that the given value is a valid e-mail address, using a
moderately complex regular expression. moderately complex regular expression.
* Error message keys: ``required``, ``invalid``
Has two optional arguments for validation, ``max_length`` and ``min_length``. Has two optional arguments for validation, ``max_length`` and ``min_length``.
If provided, these arguments ensure that the string is at most or at least the If provided, these arguments ensure that the string is at most or at least the
@ -1269,6 +1302,7 @@ given length.
* Normalizes to: An ``UploadedFile`` object that wraps the file content * Normalizes to: An ``UploadedFile`` object that wraps the file content
and file name into a single object. and file name into a single object.
* Validates that non-empty file data has been bound to the form. * Validates that non-empty file data has been bound to the form.
* Error message keys: ``required``, ``invalid``, ``missing``, ``empty``
An ``UploadedFile`` object has two attributes: An ``UploadedFile`` object has two attributes:
@ -1299,6 +1333,8 @@ When you use a ``FileField`` on a form, you must also remember to
and file name into a single object. and file name into a single object.
* Validates that file data has been bound to the form, and that the * Validates that file data has been bound to the form, and that the
file is of an image format understood by PIL. file is of an image format understood by PIL.
* Error message keys: ``required``, ``invalid``, ``missing``, ``empty``,
``invalid_image``
Using an ImageField requires that the `Python Imaging Library`_ is installed. Using an ImageField requires that the `Python Imaging Library`_ is installed.
@ -1315,6 +1351,8 @@ When you use a ``FileField`` on a form, you must also remember to
* Normalizes to: A Python integer or long integer. * Normalizes to: A Python integer or long integer.
* Validates that the given value is an integer. Leading and trailing * Validates that the given value is an integer. Leading and trailing
whitespace is allowed, as in Python's ``int()`` function. whitespace is allowed, as in Python's ``int()`` function.
* Error message keys: ``required``, ``invalid``, ``max_value``,
``min_value``
Takes two optional arguments for validation, ``max_value`` and ``min_value``. Takes two optional arguments for validation, ``max_value`` and ``min_value``.
These control the range of values permitted in the field. These control the range of values permitted in the field.
@ -1327,6 +1365,7 @@ These control the range of values permitted in the field.
* Normalizes to: A Unicode object. * Normalizes to: A Unicode object.
* Validates that the given value is a valid IPv4 address, using a regular * Validates that the given value is a valid IPv4 address, using a regular
expression. expression.
* Error message keys: ``required``, ``invalid``
``MultipleChoiceField`` ``MultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
@ -1336,6 +1375,7 @@ These control the range of values permitted in the field.
* Normalizes to: A list of Unicode objects. * Normalizes to: A list of Unicode objects.
* Validates that every value in the given list of values exists in the list * Validates that every value in the given list of values exists in the list
of choices. of choices.
* Error message keys: ``required``, ``invalid_choice``, ``invalid_list``
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
tuple) of 2-tuples to use as choices for this field. tuple) of 2-tuples to use as choices for this field.
@ -1356,6 +1396,7 @@ tuple) of 2-tuples to use as choices for this field.
* Normalizes to: A Unicode object. * Normalizes to: A Unicode object.
* Validates that the given value matches against a certain regular * Validates that the given value matches against a certain regular
expression. expression.
* Error message keys: ``required``, ``invalid``
Takes one required argument, ``regex``, which is a regular expression specified Takes one required argument, ``regex``, which is a regular expression specified
either as a string or a compiled regular expression object. either as a string or a compiled regular expression object.
@ -1367,11 +1408,13 @@ Also takes the following optional arguments:
====================== ===================================================== ====================== =====================================================
``max_length`` Ensures the string has at most this many characters. ``max_length`` Ensures the string has at most this many characters.
``min_length`` Ensures the string has at least this many characters. ``min_length`` Ensures the string has at least this many characters.
``error_message`` Error message to return for failed validation. If no
message is provided, a generic error message will be
used.
====================== ===================================================== ====================== =====================================================
The optional argument ``error_message`` is also accepted for backwards
compatibility. The preferred way to provide an error message is to use the
``error_messages`` argument, passing a dictionary with ``'invalid'`` as a key
and the error message as the value.
``TimeField`` ``TimeField``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -1380,6 +1423,7 @@ Also takes the following optional arguments:
* Normalizes to: A Python ``datetime.time`` object. * Normalizes to: A Python ``datetime.time`` object.
* Validates that the given value is either a ``datetime.time`` or string * Validates that the given value is either a ``datetime.time`` or string
formatted in a particular time format. formatted in a particular time format.
* Error message keys: ``required``, ``invalid``
Takes one optional argument, ``input_formats``, which is a list of formats used Takes one optional argument, ``input_formats``, which is a list of formats used
to attempt to convert a string to a valid ``datetime.time`` object. to attempt to convert a string to a valid ``datetime.time`` object.
@ -1396,6 +1440,7 @@ If no ``input_formats`` argument is provided, the default input formats are::
* Empty value: ``''`` (an empty string) * Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object. * Normalizes to: A Unicode object.
* Validates that the given value is a valid URL. * Validates that the given value is a valid URL.
* Error message keys: ``required``, ``invalid``, ``invalid_link``
Takes the following optional arguments: Takes the following optional arguments:

View File

@ -47,14 +47,14 @@ This is useful if you want to serialize data directly to a file-like object
Subset of fields Subset of fields
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
If you only want a subset of fields to be serialized, you can If you only want a subset of fields to be serialized, you can
specify a ``fields`` argument to the serializer:: specify a ``fields`` argument to the serializer::
from django.core import serializers from django.core import serializers
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size')) data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))
In this example, only the ``name`` and ``size`` attributes of each model will In this example, only the ``name`` and ``size`` attributes of each model will
be serialized. be serialized.
.. note:: .. note::
@ -111,9 +111,9 @@ Django "ships" with a few included serializers:
``python`` Translates to and from "simple" Python objects (lists, dicts, ``python`` Translates to and from "simple" Python objects (lists, dicts,
strings, etc.). Not really all that useful on its own, but strings, etc.). Not really all that useful on its own, but
used as a base for other serializers. used as a base for other serializers.
``yaml`` Serializes to YAML (Yet Another Markup Lanuage). This ``yaml`` Serializes to YAML (Yet Another Markup Lanuage). This
serializer is only available if PyYAML_ is installed. serializer is only available if PyYAML_ is installed.
========== ============================================================== ========== ==============================================================
.. _json: http://json.org/ .. _json: http://json.org/
@ -135,6 +135,23 @@ For example::
json_serializer = serializers.get_serializer("json")() json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response) json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
Django ships with a copy of simplejson_ in the source. Be aware, that if
you're using that for serializing directly that not all Django output can be
passed unmodified to simplejson. In particular, `lazy translation objects`_
need a `special encoder`_ written for them. Something like this will work::
from django.utils.functional import Promise
from django.utils.encoding import force_unicode
class LazyEncoder(simplejson.JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
.. _lazy translation objects: ../i18n/#lazy-translation
.. _special encoder: http://svn.red-bean.com/bob/simplejson/tags/simplejson-1.7/docs/index.html
Writing custom serializers Writing custom serializers
`````````````````````````` ``````````````````````````

View File

@ -740,7 +740,7 @@ Available format strings:
if they're zero and the special-case if they're zero and the special-case
strings 'midnight' and 'noon' if strings 'midnight' and 'noon' if
appropriate. Proprietary extension. appropriate. Proprietary extension.
r RFC 822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'`` r RFC 2822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'``
s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'`` s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'``
S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'`` S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'``
month, 2 characters. month, 2 characters.
@ -1106,25 +1106,39 @@ floatformat
When used without an argument, rounds a floating-point number to one decimal When used without an argument, rounds a floating-point number to one decimal
place -- but only if there's a decimal part to be displayed. For example: place -- but only if there's a decimal part to be displayed. For example:
* ``36.123`` gets converted to ``36.1`` ======== ======================= ======
* ``36.15`` gets converted to ``36.2`` value Template Output
* ``36`` gets converted to ``36`` ======== ======================= ======
34.23234 {{ value|floatformat }} 34.2
34.00000 {{ value|floatformat }} 34
34.26000 {{ value|floatformat }} 34.3
======== ======================= ======
If used with a numeric integer argument, ``floatformat`` rounds a number to that If used with a numeric integer argument, ``floatformat`` rounds a number to
many decimal places. For example: that many decimal places. For example:
* ``36.1234`` with floatformat:3 gets converted to ``36.123`` ======== ========================= ======
* ``36`` with floatformat:4 gets converted to ``36.0000`` value Template Output
======== ========================= ======
34.23234 {{ value|floatformat:3 }} 34.232
34.00000 {{ value|floatformat:3 }} 34.000
34.26000 {{ value|floatformat:3 }} 34.260
======== ========================= ======
If the argument passed to ``floatformat`` is negative, it will round a number to If the argument passed to ``floatformat`` is negative, it will round a number
that many decimal places -- but only if there's a decimal part to be displayed. to that many decimal places -- but only if there's a decimal part to be
For example: displayed. For example:
* ``36.1234`` with floatformat:-3 gets converted to ``36.123`` ======== ============================ ======
* ``36`` with floatformat:-4 gets converted to ``36`` value Template Output
======== ============================ ======
34.23234 {{ value|floatformat:"-3" }} 34.232
34.00000 {{ value|floatformat:"-3" }} 34
34.26000 {{ value|floatformat:"-3" }} 34.260
======== ============================ ======
Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with Using ``floatformat`` with no argument is equivalent to using ``floatformat``
an argument of ``-1``. with an argument of ``-1``.
get_digit get_digit
~~~~~~~~~ ~~~~~~~~~

View File

@ -721,7 +721,6 @@ This means, instead of instantiating a ``Client`` in each test::
...you can just refer to ``self.client``, like so:: ...you can just refer to ``self.client``, like so::
from django.test import TestCase from django.test import TestCase
from django.test.client import Client
class SimpleTest(TestCase): class SimpleTest(TestCase):
def test_details(self): def test_details(self):

View File

@ -0,0 +1,106 @@
"""
Tests for field subclassing.
"""
from django.db import models
from django.utils.encoding import force_unicode
from django.core import serializers
class Small(object):
"""
A simple class to show that non-trivial Python objects can be used as
attributes.
"""
def __init__(self, first, second):
self.first, self.second = first, second
def __unicode__(self):
return u'%s%s' % (force_unicode(self.first), force_unicode(self.second))
def __str__(self):
return unicode(self).encode('utf-8')
class SmallField(models.Field):
"""
Turns the "Small" class into a Django field. Because of the similarities
with normal character fields and the fact that Small.__unicode__ does
something sensible, we don't need to implement a lot here.
"""
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 2
super(SmallField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return 'CharField'
def to_python(self, value):
if isinstance(value, Small):
return value
return Small(value[0], value[1])
def get_db_prep_save(self, value):
return unicode(value)
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'exact':
return force_unicode(value)
if lookup_type == 'in':
return [force_unicode(v) for v in value]
if lookup_type == 'isnull':
return []
raise TypeError('Invalid lookup type: %r' % lookup_type)
def flatten_data(self, follow, obj=None):
return {self.attname: force_unicode(self._get_val_from_obj(obj))}
class MyModel(models.Model):
name = models.CharField(max_length=10)
data = SmallField('small field')
def __unicode__(self):
return force_unicode(self.name)
__test__ = {'API_TESTS': ur"""
# Creating a model with custom fields is done as per normal.
>>> s = Small(1, 2)
>>> print s
12
>>> m = MyModel(name='m', data=s)
>>> m.save()
# Custom fields still have normal field's attributes.
>>> m._meta.get_field('data').verbose_name
'small field'
# The m.data attribute has been initialised correctly. It's a Small object.
>>> m.data.first, m.data.second
(1, 2)
# The data loads back from the database correctly and 'data' has the right type.
>>> m1 = MyModel.objects.get(pk=m.pk)
>>> isinstance(m1.data, Small)
True
>>> print m1.data
12
# We can do normal filtering on the custom field (and will get an error when we
# use a lookup type that does not make sense).
>>> s1 = Small(1, 3)
>>> s2 = Small('a', 'b')
>>> MyModel.objects.filter(data__in=[s, s1, s2])
[<MyModel: m>]
>>> MyModel.objects.filter(data__lt=s)
Traceback (most recent call last):
...
TypeError: Invalid lookup type: 'lt'
# Serialization works, too.
>>> stream = serializers.serialize("json", MyModel.objects.all())
>>> stream
'[{"pk": 1, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]'
>>> obj = list(serializers.deserialize("json", stream))[0]
>>> obj.object == m
True
"""}

View File

@ -17,6 +17,10 @@ u'0'
u'7.700' u'7.700'
>>> floatformat(6.000000,3) >>> floatformat(6.000000,3)
u'6.000' u'6.000'
>>> floatformat(6.200000, 3)
u'6.200'
>>> floatformat(6.200000, -3)
u'6.200'
>>> floatformat(13.1031,-3) >>> floatformat(13.1031,-3)
u'13.103' u'13.103'
>>> floatformat(11.1197, -2) >>> floatformat(11.1197, -2)

View File

@ -0,0 +1,315 @@
# -*- coding: utf-8 -*-
tests = r"""
>>> from django.newforms import *
# CharField ###################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s'
>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s'
>>> f = CharField(min_length=5, max_length=10, error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('1234')
Traceback (most recent call last):
...
ValidationError: [u'LENGTH 4, MIN LENGTH 5']
>>> f.clean('12345678901')
Traceback (most recent call last):
...
ValidationError: [u'LENGTH 11, MAX LENGTH 10']
# IntegerField ################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> e['min_value'] = 'MIN VALUE IS %s'
>>> e['max_value'] = 'MAX VALUE IS %s'
>>> f = IntegerField(min_value=5, max_value=10, error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean('4')
Traceback (most recent call last):
...
ValidationError: [u'MIN VALUE IS 5']
>>> f.clean('11')
Traceback (most recent call last):
...
ValidationError: [u'MAX VALUE IS 10']
# FloatField ##################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> e['min_value'] = 'MIN VALUE IS %s'
>>> e['max_value'] = 'MAX VALUE IS %s'
>>> f = FloatField(min_value=5, max_value=10, error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean('4')
Traceback (most recent call last):
...
ValidationError: [u'MIN VALUE IS 5']
>>> f.clean('11')
Traceback (most recent call last):
...
ValidationError: [u'MAX VALUE IS 10']
# DecimalField ################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> e['min_value'] = 'MIN VALUE IS %s'
>>> e['max_value'] = 'MAX VALUE IS %s'
>>> e['max_digits'] = 'MAX DIGITS IS %s'
>>> e['max_decimal_places'] = 'MAX DP IS %s'
>>> e['max_whole_digits'] = 'MAX DIGITS BEFORE DP IS %s'
>>> f = DecimalField(min_value=5, max_value=10, error_messages=e)
>>> f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean('4')
Traceback (most recent call last):
...
ValidationError: [u'MIN VALUE IS 5']
>>> f.clean('11')
Traceback (most recent call last):
...
ValidationError: [u'MAX VALUE IS 10']
>>> f2.clean('123.45')
Traceback (most recent call last):
...
ValidationError: [u'MAX DIGITS IS 4']
>>> f2.clean('1.234')
Traceback (most recent call last):
...
ValidationError: [u'MAX DP IS 2']
>>> f2.clean('123.4')
Traceback (most recent call last):
...
ValidationError: [u'MAX DIGITS BEFORE DP IS 2']
# DateField ###################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> f = DateField(error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
# TimeField ###################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> f = TimeField(error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
# DateTimeField ###############################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> f = DateTimeField(error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
# RegexField ##################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s'
>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s'
>>> f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abcde')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean('1234')
Traceback (most recent call last):
...
ValidationError: [u'LENGTH 4, MIN LENGTH 5']
>>> f.clean('12345678901')
Traceback (most recent call last):
...
ValidationError: [u'LENGTH 11, MAX LENGTH 10']
# EmailField ##################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s'
>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s'
>>> f = EmailField(min_length=8, max_length=10, error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abcdefgh')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean('a@b.com')
Traceback (most recent call last):
...
ValidationError: [u'LENGTH 7, MIN LENGTH 8']
>>> f.clean('aye@bee.com')
Traceback (most recent call last):
...
ValidationError: [u'LENGTH 11, MAX LENGTH 10']
# FileField ##################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> e['missing'] = 'MISSING'
>>> e['empty'] = 'EMPTY FILE'
>>> f = FileField(error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean({})
Traceback (most recent call last):
...
ValidationError: [u'MISSING']
>>> f.clean({'filename': 'name', 'content':''})
Traceback (most recent call last):
...
ValidationError: [u'EMPTY FILE']
# URLField ##################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID'
>>> e['invalid_link'] = 'INVALID LINK'
>>> f = URLField(verify_exists=True, error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('abc.c')
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com')
Traceback (most recent call last):
...
ValidationError: [u'INVALID LINK']
# BooleanField ################################################################
>>> e = {'required': 'REQUIRED'}
>>> f = BooleanField(error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
# ChoiceField #################################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE'
>>> f = ChoiceField(choices=[('a', 'aye')], error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('b')
Traceback (most recent call last):
...
ValidationError: [u'b IS INVALID CHOICE']
# MultipleChoiceField #########################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE'
>>> e['invalid_list'] = 'NOT A LIST'
>>> f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('b')
Traceback (most recent call last):
...
ValidationError: [u'NOT A LIST']
>>> f.clean(['b'])
Traceback (most recent call last):
...
ValidationError: [u'b IS INVALID CHOICE']
# SplitDateTimeField ##########################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid_date'] = 'INVALID DATE'
>>> e['invalid_time'] = 'INVALID TIME'
>>> f = SplitDateTimeField(error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean(['a', 'b'])
Traceback (most recent call last):
...
ValidationError: [u'INVALID DATE', u'INVALID TIME']
# IPAddressField ##############################################################
>>> e = {'required': 'REQUIRED'}
>>> e['invalid'] = 'INVALID IP ADDRESS'
>>> f = IPAddressField(error_messages=e)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'REQUIRED']
>>> f.clean('127.0.0')
Traceback (most recent call last):
...
ValidationError: [u'INVALID IP ADDRESS']
"""

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
tests = r""" tests = r"""
>>> from django.newforms import * >>> from django.newforms import *
>>> from django.utils.encoding import force_unicode
>>> import datetime >>> import datetime
>>> import time >>> import time
>>> import re >>> import re
@ -362,7 +363,7 @@ u'sirrobin'
... return self.as_divs() ... return self.as_divs()
... def as_divs(self): ... def as_divs(self):
... if not self: return u'' ... if not self: return u''
... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % e for e in self]) ... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % force_unicode(e) for e in self])
>>> class CommentForm(Form): >>> class CommentForm(Form):
... name = CharField(max_length=50, required=False) ... name = CharField(max_length=50, required=False)
... email = EmailField() ... email = EmailField()

View File

@ -26,7 +26,6 @@ There were some problems with form translations in #3600
Translations are done at rendering time, so multi-lingual apps can define forms Translations are done at rendering time, so multi-lingual apps can define forms
early and still send back the right translation. early and still send back the right translation.
# XFAIL
>>> activate('de') >>> activate('de')
>>> print f.as_p() >>> print f.as_p()
<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p> <p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>

View File

@ -2,6 +2,7 @@
from extra import tests as extra_tests from extra import tests as extra_tests
from fields import tests as fields_tests from fields import tests as fields_tests
from forms import tests as form_tests from forms import tests as form_tests
from error_messages import tests as custom_error_message_tests
from localflavor.ar import tests as localflavor_ar_tests from localflavor.ar import tests as localflavor_ar_tests
from localflavor.au import tests as localflavor_au_tests from localflavor.au import tests as localflavor_au_tests
from localflavor.br import tests as localflavor_br_tests from localflavor.br import tests as localflavor_br_tests
@ -31,6 +32,7 @@ __test__ = {
'extra_tests': extra_tests, 'extra_tests': extra_tests,
'fields_tests': fields_tests, 'fields_tests': fields_tests,
'form_tests': form_tests, 'form_tests': form_tests,
'custom_error_message_tests': custom_error_message_tests,
'localflavor_ar_tests': localflavor_ar_tests, 'localflavor_ar_tests': localflavor_ar_tests,
'localflavor_au_tests': localflavor_au_tests, 'localflavor_au_tests': localflavor_au_tests,
'localflavor_br_tests': localflavor_br_tests, 'localflavor_br_tests': localflavor_br_tests,

View File

@ -42,4 +42,11 @@ u''
# Can take a mixture in a list. # Can take a mixture in a list.
>>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages >>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul> <ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
>>> class VeryBadError:
... def __unicode__(self): return u"A very bad error."
# Can take a non-string.
>>> print ValidationError(VeryBadError()).messages
<ul class="errorlist"><li>A very bad error.</li></ul>
""" """

View File

@ -7,7 +7,7 @@ urlpatterns = patterns('',
# Test urls for testing reverse lookups # Test urls for testing reverse lookups
(r'^$', views.index), (r'^$', views.index),
(r'^client/(\d+)/$', views.client), (r'^client/(\d+)/$', views.client),
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action), (r'^client/(?P<id>\d+)/(?P<action>[^/]+)/$', views.client_action),
url(r'^named-client/(\d+)/$', views.client, name="named.client"), url(r'^named-client/(\d+)/$', views.client, name="named.client"),
# Unicode strings are permitted everywhere. # Unicode strings are permitted everywhere.

View File

@ -27,6 +27,14 @@ u'Paris+%26+Orl%C3%A9ans'
>>> urlquote_plus(u'Paris & Orl\xe9ans', safe="&") >>> urlquote_plus(u'Paris & Orl\xe9ans', safe="&")
u'Paris+&+Orl%C3%A9ans' u'Paris+&+Orl%C3%A9ans'
### cookie_date, http_date ###############################################
>>> from django.utils.http import cookie_date, http_date
>>> t = 1167616461.0
>>> cookie_date(t)
'Mon, 01-Jan-2007 01:54:21 GMT'
>>> http_date(t)
'Mon, 01 Jan 2007 01:54:21 GMT'
### iri_to_uri ########################################################### ### iri_to_uri ###########################################################
>>> from django.utils.encoding import iri_to_uri >>> from django.utils.encoding import iri_to_uri
>>> iri_to_uri(u'red%09ros\xe9#red') >>> iri_to_uri(u'red%09ros\xe9#red')