1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +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
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:138

View File

@ -104,7 +104,7 @@ class PasswordResetForm(oldforms.Manipulator):
'site_name': site_name,
'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):
"A form that lets a user change his password."

View File

@ -1,8 +1,8 @@
import time
from django.conf import settings
from django.utils.cache import patch_vary_headers
from email.Utils import formatdate
import datetime
import time
from django.utils.http import cookie_date
TEST_COOKIE_NAME = 'testcookie'
TEST_COOKIE_VALUE = 'worked'
@ -11,7 +11,8 @@ class SessionMiddleware(object):
def process_request(self, request):
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):
# If request.session was modified, or if response.session was set, save
@ -30,13 +31,8 @@ class SessionMiddleware(object):
expires = None
else:
max_age = settings.SESSION_COOKIE_AGE
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
# 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")
expires_time = time.time() + settings.SESSION_COOKIE_AGE
expires = cookie_date(expires_time)
# Save the seesion data and refresh the client cookie.
request.session.save()
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
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
# 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
from django.core.management.base import copy_helper, CommandError, 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]"
label = 'application name'
@ -14,17 +16,18 @@ class Command(LabelCommand):
def handle_label(self, app_name, directory=None, **options):
if directory is None:
directory = os.getcwd()
# Determine the project_name a bit naively -- by looking at the name of
# the parent directory.
project_dir = os.path.normpath(os.path.join(directory, os.pardir))
parent_dir = os.path.basename(project_dir)
# Determine the project_name by using the basename of directory,
# which should be the full path of the project directory (or the
# current directory if no directory was passed).
project_name = os.path.basename(directory)
if app_name == project_name:
raise CommandError("You cannot create an app with the same name (%r) as your project." % app_name)
copy_helper(self.style, 'app', app_name, directory, parent_dir)
raise CommandError("You cannot create an app with the same name"
" (%r) as your project." % app_name)
copy_helper(self.style, 'app', app_name, directory, project_name)
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):
super(ProjectCommand, self).__init__()

View File

@ -252,6 +252,7 @@ def sql_model_create(model, style, known_models=set()):
table_output = []
pending_references = {}
qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
for f in opts.fields:
col_type = f.db_type()
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.
field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
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') + ' ' + \
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)) + ')' +
@ -341,10 +342,12 @@ def sql_for_pending_references(model, style, pending_references):
def many_to_many_sql_for_model(model, style):
from django.db import connection, models
from django.contrib.contenttypes import generic
from django.db.backends.util import truncate_name
opts = model._meta
final_output = []
qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
for f in opts.many_to_many:
if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
@ -354,26 +357,43 @@ def many_to_many_sql_for_model(model, style):
tablespace_sql = ''
table_output = [style.SQL_KEYWORD('CREATE 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_COLTYPE(models.AutoField(primary_key=True).db_type()),
style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
tablespace_sql))
table_output.append(' %s %s %s %s (%s)%s,' % \
if inline_references:
deferred = []
table_output.append(' %s %s %s %s (%s)%s,' %
(style.SQL_FIELD(qn(f.m2m_column_name())),
style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
style.SQL_KEYWORD('NOT NULL REFERENCES'),
style.SQL_TABLE(qn(opts.db_table)),
style.SQL_FIELD(qn(opts.pk.column)),
connection.ops.deferrable_sql()))
table_output.append(' %s %s %s %s (%s)%s,' % \
table_output.append(' %s %s %s %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 REFERENCES'),
style.SQL_TABLE(qn(f.rel.to._meta.db_table)),
style.SQL_FIELD(qn(f.rel.to._meta.pk.column)),
connection.ops.deferrable_sql()))
table_output.append(' %s (%s, %s)%s' % \
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_FIELD(qn(f.m2m_column_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(';')
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
autoinc_sql = connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
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 types import ListType, StringType
from email.Utils import formatdate
import mimetypes
import os
import re
import sys
import time
import urllib
from django.utils.http import http_date
__version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler','demo_app']
@ -376,7 +376,7 @@ class ServerHandler(object):
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
if 'Date' not in self.headers:
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:
self._write('Server: %s\r\n' % self.server_software)

View File

@ -43,6 +43,7 @@ class BaseDatabaseFeatures(object):
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = True
inline_fk_references = True
needs_datetime_string_cast = True
needs_upper_for_iops = False
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):
autoindexes_primary_keys = False
inline_fk_references = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):

View File

@ -65,6 +65,7 @@ class MysqlDebugWrapper:
class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
class DatabaseOperations(BaseDatabaseOperations):
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.base import Model
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 import signals
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.
data_types = get_creation_module().DATA_TYPES
internal_type = self.get_internal_type()
if internal_type not in data_types:
return None
return data_types[internal_type] % self.__dict__
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):
"""
@ -11,7 +11,7 @@ class ConditionalGetMiddleware(object):
Also sets the Date and Content-Length response-headers.
"""
def process_response(self, request, response):
response['Date'] = formatdate()[:26] + "GMT"
response['Date'] = http_date()
if not response.has_header('Content-Length'):
response['Content-Length'] = str(len(response.content))
@ -23,7 +23,6 @@ class ConditionalGetMiddleware(object):
response['Content-Length'] = '0'
if response.has_header('Last-Modified'):
last_mod = response['Last-Modified']
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
if if_modified_since == response['Last-Modified']:
response.status_code = 304

View File

@ -1,30 +1,35 @@
"""
Field classes
Field classes.
"""
import copy
import datetime
import re
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 util import ErrorList, ValidationError
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__ = (
'Field', 'CharField', 'IntegerField',
'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField',
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField',
)
@ -32,24 +37,20 @@ __all__ = (
# These values, if given to to_python(), will trigger the self.required check.
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):
widget = TextInput # Default widget to use when rendering this type of Field.
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.
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.
# True by default.
# 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
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):
"""
Validates the given value and returns its "cleaned" value as an
@ -90,7 +107,7 @@ class Field(object):
Raises ValidationError for any errors.
"""
if self.required and value in EMPTY_VALUES:
raise ValidationError(ugettext(u'This field is required.'))
raise ValidationError(self.error_messages['required'])
return value
def widget_attrs(self, widget):
@ -108,6 +125,11 @@ class Field(object):
return result
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):
self.max_length, self.min_length = max_length, min_length
super(CharField, self).__init__(*args, **kwargs)
@ -120,9 +142,9 @@ class CharField(Field):
value = smart_unicode(value)
value_length = len(value)
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:
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
def widget_attrs(self, widget):
@ -131,6 +153,12 @@ class CharField(Field):
return {'maxlength': str(self.max_length)}
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):
self.max_value, self.min_value = max_value, min_value
super(IntegerField, self).__init__(*args, **kwargs)
@ -146,14 +174,20 @@ class IntegerField(Field):
try:
value = int(str(value))
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:
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:
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
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):
self.max_value, self.min_value = max_value, min_value
Field.__init__(self, *args, **kwargs)
@ -169,14 +203,23 @@ class FloatField(Field):
try:
value = float(value)
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:
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:
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
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):
self.max_value, self.min_value = max_value, min_value
self.max_digits, self.decimal_places = max_digits, decimal_places
@ -196,20 +239,20 @@ class DecimalField(Field):
try:
value = Decimal(value)
except DecimalException:
raise ValidationError(ugettext('Enter a number.'))
raise ValidationError(self.error_messages['invalid'])
pieces = str(value).lstrip("-").split('.')
decimals = (len(pieces) == 2) and len(pieces[1]) or 0
digits = len(pieces[0])
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:
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:
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:
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):
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
DEFAULT_DATE_INPUT_FORMATS = (
@ -221,6 +264,10 @@ DEFAULT_DATE_INPUT_FORMATS = (
)
class DateField(Field):
default_error_messages = {
'invalid': _(u'Enter a valid date.'),
}
def __init__(self, input_formats=None, *args, **kwargs):
super(DateField, self).__init__(*args, **kwargs)
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])
except ValueError:
continue
raise ValidationError(ugettext(u'Enter a valid date.'))
raise ValidationError(self.error_messages['invalid'])
DEFAULT_TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
@ -250,6 +297,10 @@ DEFAULT_TIME_INPUT_FORMATS = (
)
class TimeField(Field):
default_error_messages = {
'invalid': _(u'Enter a valid time.')
}
def __init__(self, input_formats=None, *args, **kwargs):
super(TimeField, self).__init__(*args, **kwargs)
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])
except ValueError:
continue
raise ValidationError(ugettext(u'Enter a valid time.'))
raise ValidationError(self.error_messages['invalid'])
DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
@ -285,6 +336,9 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
class DateTimeField(Field):
widget = DateTimeInput
default_error_messages = {
'invalid': _(u'Enter a valid date/time.'),
}
def __init__(self, input_formats=None, *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
# components: date and time.
if len(value) != 2:
raise ValidationError(ugettext(u'Enter a valid date/time.'))
raise ValidationError(self.error_messages['invalid'])
value = '%s %s' % tuple(value)
for format in self.input_formats:
try:
return datetime.datetime(*time.strptime(value, format)[:6])
except ValueError:
continue
raise ValidationError(ugettext(u'Enter a valid date/time.'))
raise ValidationError(self.error_messages['invalid'])
class RegexField(CharField):
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
'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)
if isinstance(regex, basestring):
regex = re.compile(regex)
self.regex = regex
self.error_message = error_message or ugettext(u'Enter a valid value.')
def clean(self, value):
"""
@ -337,7 +395,7 @@ class RegexField(CharField):
if value == u'':
return value
if not self.regex.search(value):
raise ValidationError(self.error_message)
raise ValidationError(self.error_messages['invalid'])
return value
email_re = re.compile(
@ -346,9 +404,13 @@ email_re = re.compile(
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
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):
RegexField.__init__(self, email_re, max_length, min_length,
ugettext(u'Enter a valid e-mail address.'), *args, **kwargs)
RegexField.__init__(self, email_re, max_length, min_length, *args,
**kwargs)
try:
from django.conf import settings
@ -372,6 +434,12 @@ class UploadedFile(StrAndUnicode):
class FileField(Field):
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):
super(FileField, self).__init__(*args, **kwargs)
@ -382,14 +450,18 @@ class FileField(Field):
try:
f = UploadedFile(data['filename'], data['content'])
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:
raise ValidationError(ugettext(u"No file was submitted."))
raise ValidationError(self.error_messages['missing'])
if not f.content:
raise ValidationError(ugettext(u"The submitted file is empty."))
raise ValidationError(self.error_messages['empty'])
return f
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):
"""
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.verify()
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
url_re = re.compile(
@ -422,9 +494,15 @@ url_re = re.compile(
r'(?:/?|/\S+)$', re.IGNORECASE)
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,
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.user_agent = validator_user_agent
@ -449,9 +527,9 @@ class URLField(RegexField):
req = urllib2.Request(value, None, headers)
u = urllib2.urlopen(req)
except ValueError:
raise ValidationError(ugettext(u'Enter a valid URL.'))
raise ValidationError(self.error_messages['invalid'])
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
class BooleanField(Field):
@ -478,9 +556,14 @@ class NullBooleanField(BooleanField):
class ChoiceField(Field):
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):
super(ChoiceField, self).__init__(required, widget, label, initial, help_text)
def __init__(self, choices=(), required=True, widget=None, label=None,
initial=None, help_text=None, *args, **kwargs):
super(ChoiceField, self).__init__(required, widget, label, initial,
help_text, *args, **kwargs)
self.choices = choices
def _get_choices(self):
@ -506,29 +589,33 @@ class ChoiceField(Field):
return value
valid_values = set([smart_unicode(k) for k, v in self.choices])
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
class MultipleChoiceField(ChoiceField):
hidden_widget = MultipleHiddenInput
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):
"""
Validates that the input is a list or tuple.
"""
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:
return []
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]
# Validate that each value in the value list is in self.choices.
valid_values = set([smart_unicode(k) for k, v in self.choices])
for val in new_value:
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
class ComboField(Field):
@ -571,6 +658,10 @@ class MultiValueField(Field):
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):
super(MultiValueField, self).__init__(*args, **kwargs)
# 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 not [v for v in value if v not in EMPTY_VALUES]:
if self.required:
raise ValidationError(ugettext(u'This field is required.'))
raise ValidationError(self.error_messages['required'])
else:
return self.compress([])
else:
raise ValidationError(ugettext(u'Enter a list of values.'))
raise ValidationError(self.error_messages['invalid'])
for i, field in enumerate(self.fields):
try:
field_value = value[i]
except IndexError:
field_value = None
if self.required and field_value in EMPTY_VALUES:
raise ValidationError(ugettext(u'This field is required.'))
raise ValidationError(self.error_messages['required'])
try:
clean_data.append(field.clean(field_value))
except ValidationError, e:
@ -629,8 +720,19 @@ class MultiValueField(Field):
raise NotImplementedError('Subclasses must implement this method.')
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):
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)
def compress(self, data_list):
@ -638,16 +740,18 @@ class SplitDateTimeField(MultiValueField):
# Raise a validation error if time or date is empty
# (possible if SplitDateTimeField has required=False).
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:
raise ValidationError(ugettext(u'Enter a valid time.'))
raise ValidationError(self.error_messages['invalid_time'])
return datetime.datetime.combine(*data_list)
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}$')
class IPAddressField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid IPv4 address.'),
}
def __init__(self, *args, **kwargs):
RegexField.__init__(self, ipv4_re,
error_message=ugettext(u'Enter a valid IPv4 address.'),
*args, **kwargs)
super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)

View File

@ -6,7 +6,6 @@ and database field objects.
from django.utils.translation import ugettext
from django.utils.encoding import smart_unicode
from util import ValidationError
from forms import BaseForm, SortedDictFromList
from fields import Field, ChoiceField, IntegerField
@ -19,7 +18,8 @@ __all__ = (
'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``.
@ -29,15 +29,17 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
from django.db import models
opts = instance.__class__._meta
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
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
if fields and f.name not in fields:
continue
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():
opts = instance.__class__._meta
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:
f.save_form_data(instance, cleaned_data[f.name])
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()
save_m2m()
else:
# 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
return instance
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):
return save_instance(self, model(), fields, fail_message, commit)
return save
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):
return save_instance(self, instance, fields, fail_message, commit)
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.
@ -90,9 +93,11 @@ def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda
field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list)
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.
@ -117,16 +122,22 @@ def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=l
field_list.append((f.name, formfield))
base_fields = SortedDictFromList(field_list)
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):
"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})
class QuerySetIterator(object):
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):
if self.empty_label is not None:
@ -138,11 +149,13 @@ class QuerySetIterator(object):
self.queryset._result_cache = None
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
# actually use any of ChoiceField's implementation.
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.empty_label = empty_label
self.cache_choices = cache_choices
@ -162,7 +175,8 @@ class ModelChoiceField(ChoiceField):
# *each* time _get_choices() is called (and, thus, each time
# self.choices is accessed) so that we can ensure the QuerySet has not
# 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):
# This method is copied from ChoiceField._set_choices(). It's necessary
@ -179,16 +193,20 @@ class ModelChoiceField(ChoiceField):
try:
value = self.queryset.model._default_manager.get(pk=value)
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
class ModelMultipleChoiceField(ModelChoiceField):
"A MultipleChoiceField whose choices are a model QuerySet."
"""A MultipleChoiceField whose choices are a model QuerySet."""
hidden_widget = MultipleHiddenInput
def __init__(self, queryset, cache_choices=False, required=True,
widget=SelectMultiple, label=None, initial=None, help_text=None):
super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices,
required, widget, label, initial, help_text)
widget=SelectMultiple, label=None, initial=None,
help_text=None):
super(ModelMultipleChoiceField, self).__init__(queryset, None,
cache_choices, required, widget, label, initial, help_text)
def clean(self, value):
if self.required and not value:
@ -202,7 +220,9 @@ class ModelMultipleChoiceField(ModelChoiceField):
try:
obj = self.queryset.model._default_manager.get(pk=val)
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:
final_values.append(obj)
return final_values

View File

@ -42,13 +42,18 @@ class ErrorList(list, StrAndUnicode):
if not self: return u''
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):
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):
self.messages = ErrorList([smart_unicode(msg) for msg in message])
else:
assert isinstance(message, (basestring, Promise)), ("%s should be a basestring or lazy translation" % repr(message))
message = smart_unicode(message)
self.messages = ErrorList([message])
@ -58,4 +63,3 @@ class ValidationError(Exception):
# AttributeError: ValidationError instance has no attribute 'args'
# See http://www.python.org/doc/current/tut/node10.html#handling
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.conf import settings
from django.utils.translation import ugettext, ungettext
from django.utils.encoding import force_unicode, smart_str, iri_to_uri
import re
import random as random_module
from django.utils.encoding import force_unicode, iri_to_uri
register = Library()
@ -36,39 +37,48 @@ def stringfilter(func):
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("'", "\\'")
addslashes = stringfilter(addslashes)
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:]
capfirst = stringfilter(capfirst)
def fix_ampersands(value):
"Replaces ampersands with ``&amp;`` entities"
"""Replaces ampersands with ``&amp;`` entities."""
from django.utils.html import fix_ampersands
return fix_ampersands(value)
fix_ampersands = stringfilter(fix_ampersands)
def floatformat(text, arg=-1):
"""
If called without an argument, displays a floating point
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
always.
With a negative numeric argument, it will display that many decimal
places -- but only if there's places to be displayed.
Examples:
Displays a float to a specified number of decimal places.
If called without an argument, it displays the floating point number with
one decimal place -- but only if there's a decimal place to be displayed:
* num1 = 34.23234
* num2 = 34.00000
* num1|floatformat results in 34.2
* num2|floatformat is 34
* num1|floatformat:3 is 34.232
* num2|floatformat:3 is 34.000
* num1|floatformat:-3 is 34.232
* num2|floatformat:-3 is 34
* num3 = 34.26000
* {{ num1|floatformat }} displays "34.2"
* {{ num2|floatformat }} displays "34"
* {{ num3|floatformat }} displays "34.3"
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:
f = float(text)
@ -86,15 +96,16 @@ def floatformat(text, arg=-1):
return formatstr % f
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))
iriencode = stringfilter(iriencode)
def linenumbers(value):
"Displays text with line numbers"
"""Displays text with line numbers."""
from django.utils.html import escape
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))))
for i, line in enumerate(lines):
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
@ -102,22 +113,24 @@ def linenumbers(value):
linenumbers = stringfilter(linenumbers)
def lower(value):
"Converts a string into all lowercase"
"""Converts a string into all lowercase."""
return value.lower()
lower = stringfilter(lower)
def make_list(value):
"""
Returns the value turned into a list. For an integer, it's a list of
digits. For a string, it's a list of characters.
Returns the value turned into a list.
For an integer, it's a list of digits.
For a string, it's a list of characters.
"""
return list(value)
make_list = stringfilter(make_list)
def slugify(value):
"""
Normalizes string, converts to lowercase, removes non-alpha chars and
converts spaces to hyphens.
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
"""
import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
@ -127,7 +140,8 @@ slugify = stringfilter(slugify)
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
the leading "%" is dropped.
@ -140,29 +154,29 @@ def stringformat(value, arg):
return u""
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())
title = stringfilter(title)
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
try:
length = int(arg)
except ValueError: # invalid literal for int()
except ValueError: # Invalid literal for int().
return value # Fail silently.
return truncate_words(value, length)
truncatewords = stringfilter(truncatewords)
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
try:
@ -173,26 +187,26 @@ def truncatewords_html(value, arg):
truncatewords_html = stringfilter(truncatewords_html)
def upper(value):
"Converts a string into all uppercase"
"""Converts a string into all uppercase."""
return value.upper()
upper = stringfilter(upper)
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
return urlquote(value)
urlencode = stringfilter(urlencode)
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
return urlize(value, nofollow=True)
urlize = stringfilter(urlize)
def urlizetrunc(value, limit):
"""
Converts URLs into clickable links, truncating URLs to the given character limit,
and adding 'rel=nofollow' attribute to discourage spamming.
Converts URLs into clickable links, truncating URLs to the given character
limit, and adding 'rel=nofollow' attribute to discourage spamming.
Argument: Length to truncate URLs to.
"""
@ -201,13 +215,13 @@ def urlizetrunc(value, limit):
urlizetrunc = stringfilter(urlizetrunc)
def wordcount(value):
"Returns the number of words"
"""Returns the number of words."""
return len(value.split())
wordcount = stringfilter(wordcount)
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.
"""
@ -217,29 +231,29 @@ wordwrap = stringfilter(wordwrap)
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))
ljust = stringfilter(ljust)
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))
rjust = stringfilter(rjust)
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))
center = stringfilter(center)
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'')
cut = stringfilter(cut)
@ -272,7 +286,7 @@ def linebreaksbr(value):
linebreaksbr = stringfilter(linebreaksbr)
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 = u'(%s)' % u'|'.join(tags)
starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
@ -283,7 +297,7 @@ def removetags(value, tags):
removetags = stringfilter(removetags)
def striptags(value):
"Strips all [X]HTML tags"
"""Strips all [X]HTML tags."""
from django.utils.html import strip_tags
return strip_tags(value)
striptags = stringfilter(striptags)
@ -314,29 +328,29 @@ def dictsortreversed(value, arg):
return [item[1] for item in decorated]
def first(value):
"Returns the first item in a list"
"""Returns the first item in a list."""
try:
return value[0]
except IndexError:
return u''
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:
return arg.join(map(force_unicode, value))
except AttributeError: # fail silently but nicely
return 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)
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)
def random(value):
"Returns a random item from the list"
"""Returns a random item from the list."""
return random_module.choice(value)
def slice_(value, arg):
@ -441,7 +455,7 @@ def unordered_list(value):
###################
def add(value, arg):
"Adds the arg to the value"
"""Adds the arg to the value."""
return int(value) + int(arg)
def get_digit(value, arg):
@ -468,7 +482,7 @@ def get_digit(value, arg):
###################
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
if not value:
return u''
@ -477,7 +491,7 @@ def date(value, arg=None):
return format(value, arg)
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
if value in (None, u''):
return u''
@ -486,7 +500,7 @@ def time(value, arg=None):
return time_format(value, arg)
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
if not value:
return u''
@ -495,7 +509,7 @@ def timesince(value, arg=None):
return timesince(value)
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 datetime import datetime
if not value:
@ -509,17 +523,17 @@ def timeuntil(value, arg=None):
###################
def default(value, arg):
"If value is unavailable, use given default"
"""If value is unavailable, use given default."""
return value or 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:
return arg
return value
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
def yesno(value, arg=None):
@ -544,7 +558,8 @@ def yesno(value, arg=None):
return value # Invalid arg.
try:
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]
if value is None:
return maybe
@ -558,8 +573,8 @@ def yesno(value, arg=None):
def filesizeformat(bytes):
"""
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
bytes, etc).
Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
102 bytes, etc).
"""
try:
bytes = float(bytes)
@ -576,10 +591,26 @@ def filesizeformat(bytes):
def pluralize(value, arg=u's'):
"""
Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes'
By default, 's' is used as a suffix; if an argument is provided, that string
is used instead. If the provided argument contains a comma, the text before
the comma is used for the singular case.
Returns a plural suffix if the value is not 1. By default, 's' is used as
the suffix:
* 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:
arg = u',' + arg
@ -591,23 +622,23 @@ def pluralize(value, arg=u's'):
try:
if int(value) != 1:
return plural_suffix
except ValueError: # invalid string that's not a number
except ValueError: # Invalid string that's not a number.
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:
if len(value) != 1:
return plural_suffix
except TypeError: # len() of unsized object
except TypeError: # len() of unsized object.
pass
return singular_suffix
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
return phone2numeric(value)
def pprint(value):
"A wrapper around pprint.pprint -- for debugging, really"
"""A wrapper around pprint.pprint -- for debugging, really."""
from pprint import pformat
try:
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 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.utils.encoding import smart_str, smart_unicode
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()
@ -22,14 +23,11 @@ class CommentNode(Node):
class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
self.cyclevars = cyclevars
self.cyclevars_len = len(cyclevars)
self.counter = -1
self.cycle_iter = itertools_cycle(cyclevars)
self.variable_name = variable_name
def render(self, context):
self.counter += 1
value = self.cyclevars[self.counter % self.cyclevars_len]
value = self.cycle_iter.next()
value = Variable(value).resolve(context)
if self.variable_name:
context[self.variable_name] = value
@ -49,7 +47,7 @@ class FilterNode(Node):
def render(self, context):
output = self.nodelist.render(context)
# apply filters
# Apply filters.
context.update({'var': output})
filtered = self.filter_expr.resolve(context)
context.pop()
@ -81,7 +79,8 @@ class ForNode(Node):
else:
reversed = ''
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):
for node in self.nodelist_loop:
@ -115,19 +114,20 @@ class ForNode(Node):
unpack = len(self.loopvars) > 1
for i, item in enumerate(values):
context['forloop'] = {
# shortcuts for current loop iteration number
# Shortcuts for current loop iteration number.
'counter0': i,
'counter': i+1,
# reverse counter iteration numbers
# Reverse counter iteration numbers.
'revcounter': len_values - i,
'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),
'last': (i == len_values - 1),
'parentloop': parentloop,
}
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)))
else:
context[self.loopvars[0]] = item
@ -154,8 +154,8 @@ class IfChangedNode(Node):
self._last_seen = None
try:
if self._varlist:
# Consider multiple parameters.
# This automatically behaves like a OR evaluation of the multiple variables.
# Consider multiple parameters. This automatically behaves
# like an OR evaluation of the multiple variables.
compare_to = [var.resolve(context) for var in self._varlist]
else:
compare_to = self.nodelist.render(context)
@ -249,13 +249,17 @@ class RegroupNode(Node):
def render(self, context):
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] = []
return ''
# List of dictionaries in the format
# List of dictionaries in the format:
# {'grouper': 'key', 'list': [list of contents]}.
context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))]
context[self.var_name] = [
{'grouper': key, 'list': list(val)}
for key, val in
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
]
return ''
def include_is_allowed(filepath):
@ -339,13 +343,15 @@ class URLNode(Node):
def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch
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:
return reverse(self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch:
try:
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:
return ''
@ -389,7 +395,7 @@ class WithNode(Node):
#@register.tag
def comment(parser, token):
"""
Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
"""
parser.skip_past('endcomment')
return CommentNode()
@ -398,7 +404,7 @@ comment = register.tag(comment)
#@register.tag
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
the loop::
@ -421,10 +427,10 @@ def cycle(parser, token):
interpreted as literal strings.
"""
# Note: This returns the exact same node on each {% cycle name %} call; that
# is, the node object returned from {% cycle a b c as name %} and the one
# returned from {% cycle name %} are the exact same object. This shouldn't
# cause problems (heh), but if it does, now you know.
# Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This
# shouldn't cause problems (heh), but if it does, now you know.
#
# Ugly hack warning: this stuffs the named template dict into parser so
# 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(",")]
if len(args) == 2:
# {% cycle foo %} case
# {% cycle foo %} case.
name = args[1]
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:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
@ -463,7 +470,8 @@ cycle = register.tag(cycle)
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::
@ -477,7 +485,7 @@ debug = register.tag(debug)
#@register.tag(name="filter")
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
arguments -- just like in variable syntax.
@ -526,14 +534,15 @@ def firstof(parser, token):
"""
bits = token.split_contents()[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)
firstof = register.tag(firstof)
#@register.tag(name="for")
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``::
@ -572,17 +581,20 @@ def do_for(parser, token):
"""
bits = token.contents.split()
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'
in_index = reversed and -3 or -2
if bits[in_index] != 'in':
raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents
raise TemplateSyntaxError("'for' statements should use the format"
" 'for x in y': %s" % token.contents)
loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
for var in loopvars:
if not var or ' ' in var:
raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents
raise TemplateSyntaxError("'for' tag received an invalid argument:"
" %s" % token.contents)
sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',))
@ -607,7 +619,7 @@ def do_ifequal(parser, token, negate):
#@register.tag
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::
@ -626,7 +638,10 @@ ifequal = register.tag(ifequal)
#@register.tag
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)
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"
(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 %}
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
be displayed by the ``{{ athlete_list|count }}`` variable.
As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
will be displayed if the test fails.
As you can see, the ``if`` tag can take an option ``{% else %}`` clause
that will be displayed if the test fails.
``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
variables or to negate a given variable::
@ -674,9 +687,9 @@ def do_if(parser, token):
There are some athletes and absolutely no coaches.
{% endif %}
``if`` tags do not allow ``and`` and ``or`` clauses with the same
tag, because the order of logic would be ambigous. For example,
this is invalid::
``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
because the order of logic would be ambigous. For example, this is
invalid::
{% if athlete_list and coach_list or cheerleader_list %}
@ -692,8 +705,8 @@ def do_if(parser, token):
bits = token.contents.split()
del bits[0]
if not bits:
raise TemplateSyntaxError, "'if' statement requires at least one argument"
# bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
raise TemplateSyntaxError("'if' statement requires at least one argument")
# Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
bitstr = ' '.join(bits)
boolpairs = bitstr.split(' and ')
boolvars = []
@ -728,13 +741,13 @@ do_if = register.tag("if", do_if)
#@register.tag
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.
1. Checks its own rendered contents against its previous state and only
displays the content if it has changed. For example, this displays a list of
days, only displaying the month if it changes::
displays the content if it has changed. For example, this displays a
list of days, only displaying the month if it changes::
<h1>Archive for {{ year }}</h1>
@ -743,9 +756,9 @@ def ifchanged(parser, token):
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
{% endfor %}
2. If given a variable, check whether that variable has changed. For example, the
following shows the date every time it changes, but only shows the hour if both
the hour and the date have changed::
2. If given a variable, check whether that variable has changed.
For example, the following shows the date every time it changes, but
only shows the hour if both the hour and the date have changed::
{% for date in days %}
{% ifchanged date.date %} {{ date.date }} {% endifchanged %}
@ -763,7 +776,7 @@ ifchanged = register.tag(ifchanged)
#@register.tag
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
of another file -- which must be specified using an absolute page --
@ -779,21 +792,24 @@ def ssi(parser, token):
bits = token.contents.split()
parsed = False
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 bits[2] == 'parsed':
parsed = True
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)
ssi = register.tag(ssi)
#@register.tag
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 %}
"""
@ -804,14 +820,15 @@ def load(parser, token):
lib = get_library("django.templatetags.%s" % taglib)
parser.add_library(lib)
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()
load = register.tag(load)
#@register.tag
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
for all the possible values.
@ -830,7 +847,7 @@ now = register.tag(now)
#@register.tag
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
``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
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
using it, i.e.::
people was not sorted by gender, you'd need to make sure it is sorted
before using it, i.e.::
{% regroup people|dictsort:"gender" by gender as grouped %}
@ -879,10 +896,11 @@ def regroup(parser, token):
raise TemplateSyntaxError, "'regroup' tag takes five arguments"
target = parser.compile_filter(firstbits[1])
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)
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])
@ -892,8 +910,7 @@ regroup = register.tag(regroup)
def spaceless(parser, token):
"""
Removes whitespace between HTML tags. This includes tab
characters and newlines.
Removes whitespace between HTML tags, including tab and newline characters.
Example usage::
@ -907,8 +924,8 @@ def spaceless(parser, token):
<p><a href="foo/">Foo</a></p>
Only space between *tags* is normalized -- not space between tags and text. In
this example, the space around ``Hello`` won't be stripped::
Only space between *tags* is normalized -- not space between tags and text.
In this example, the space around ``Hello`` won't be stripped::
{% spaceless %}
<strong>
@ -924,7 +941,7 @@ spaceless = register.tag(spaceless)
#@register.tag
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
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"
tag = bits[1]
if tag not in TemplateTagNode.mapping:
raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
(tag, TemplateTagNode.mapping.keys())
raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
" Must be one of: %s" %
(tag, TemplateTagNode.mapping.keys()))
return TemplateTagNode(tag)
templatetag = register.tag(templatetag)
@ -958,7 +976,8 @@ def url(parser, token):
"""
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 %}
@ -986,7 +1005,8 @@ def url(parser, token):
"""
bits = token.contents.split(' ', 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 = []
kwargs = {}
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 %}' />
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 *
100 = 87.5 which is rounded up to 88).
the above example will be 88 pixels wide (because 175/200 = .875;
.875 * 100 = 87.5 which is rounded up to 88).
"""
bits = token.contents.split()
if len(bits) != 4:
@ -1029,7 +1049,7 @@ widthratio = register.tag(widthratio)
#@register.tag
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.
For example::
@ -1040,7 +1060,8 @@ def do_with(parser, token):
"""
bits = list(token.split_contents())
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])
name = bits[3]
nodelist = parser.parse(('endwith',))

View File

@ -146,7 +146,7 @@ class TestCase(unittest.TestCase):
" context %d does not contain the"
" error '%s' (actual errors: %s)" %
(field, form, i, err,
list(field_errors)))
repr(field_errors)))
elif field in context[form].fields:
self.fail("The field '%s' on form '%s' in context %d"
" 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
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.
"""
import md5
import re
import time
from email.Utils import formatdate
from django.conf import settings
from django.core.cache import cache
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*')
@ -40,7 +41,7 @@ def patch_cache_control(response, **kwargs):
str() to it.
"""
def dictitem(s):
t = s.split('=',1)
t = s.split('=', 1)
if len(t) > 1:
return (t[0].lower(), t[1])
else:
@ -64,7 +65,7 @@ def patch_cache_control(response, **kwargs):
if 'max-age' in cc and 'max_age' in kwargs:
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 = ', '.join([dictvalue(el) for el in cc.items()])
response['Cache-Control'] = cc
@ -88,15 +89,14 @@ def patch_response_headers(response, cache_timeout=None):
if not response.has_header('ETag'):
response['ETag'] = md5.new(response.content).hexdigest()
if not response.has_header('Last-Modified'):
response['Last-Modified'] = formatdate()[:26] + "GMT"
response['Last-Modified'] = http_date()
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)
def add_never_cache_headers(response):
"""
Add headers to a response to indicate that
a page should never be cached.
Adds headers to a response to indicate that a page should never be cached.
"""
patch_response_headers(response, cache_timeout=-1)
@ -119,13 +119,14 @@ def patch_vary_headers(response, newheaders):
response['Vary'] = ', '.join(vary)
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()
for header in headerlist:
value = request.META.get(header, None)
if value is not None:
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):
"""
@ -139,7 +140,8 @@ def get_cache_key(request, key_prefix=None):
"""
if key_prefix is None:
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)
if headerlist is not None:
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
if cache_timeout is None:
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'):
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)
return _generate_cache_key(request, headerlist, key_prefix)
else:

View File

@ -170,7 +170,7 @@ class DateFormat(TimeFormat):
return u"%+03d%02d" % (seconds // 3600, (seconds // 60) % 60)
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')
def S(self):

View File

@ -1,8 +1,19 @@
import types
import urllib
import datetime
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):
"""
A class whose __str__ returns its __unicode__ as a UTF-8 bytestring.
@ -33,6 +44,7 @@ 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)):
return s
try:
if not isinstance(s, basestring,):
if hasattr(s, '__unicode__'):
s = unicode(s)
@ -40,6 +52,8 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
s = unicode(str(s), encoding, errors)
elif not isinstance(s, unicode):
s = unicode(s, encoding, errors)
except UnicodeDecodeError, e:
raise DjangoUnicodeDecodeError(s, *e.args)
return s
def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):

View File

@ -1,4 +1,6 @@
import urllib
from email.Utils import formatdate
from django.utils.encoding import smart_str, force_unicode
from django.utils.functional import allow_lazy
@ -37,3 +39,29 @@ def urlencode(query, doseq=0):
for k, v in query],
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 posixpath
import re
import rfc822
import stat
import urllib
from email.Utils import parsedate_tz, mktime_tz
from django.template import loader
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.http import http_date
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]
contents = open(fullpath, 'rb').read()
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
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
@ -119,8 +120,7 @@ def was_modified_since(header=None, mtime=0, size=0):
raise ValueError
matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
re.IGNORECASE)
header_mtime = rfc822.mktime_tz(rfc822.parsedate_tz(
matches.group(1)))
header_mtime = mktime_tz(parsedate_tz(matches.group(1)))
header_len = matches.group(3)
if header_len and int(header_len) != size:
raise ValueError

View File

@ -291,13 +291,15 @@ minutes.
Template fragment caching
=========================
**New in development version**.
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 ``{%
load cache %}`` near the top of your template.
the ``cache`` template tag. To give your template access to this tag, put
``{% load cache %}`` near the top of your template.
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
seconds, and the name to give the cache fragment. For example::
amount of time. It takes at least two arguments: the cache timeout, in seconds,
and the name to give the cache fragment. For example::
{% load cache %}
{% 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()``:
* 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.
* 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::
from django.contrib.formtools import FormPreview
from django.contrib.formtools.preview import FormPreview
from myapp.models import SomeModel
class SomeModelFormPreview(FormPreview):

View File

@ -1013,111 +1013,12 @@ See the `One-to-one relationship model example`_ for a full example.
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 --
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.
.. _Custom Model Fields: ../custom_model_fields/
Meta options
============

View File

@ -150,7 +150,7 @@ mess things up. Use the ``PythonInterpreter`` directive to give different
<Location "/otherthing">
SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
PythonInterpreter mysite_other
PythonInterpreter othersite
</Location>
</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>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
----------------------
@ -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.
* Validates that the check box is checked (i.e. the value is ``True``) if
the field has ``required=True``.
* Error message keys: ``required``
**New in Django development version:** The empty value for a ``CheckboxInput``
(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.
* Validates ``max_length`` or ``min_length``, if they are provided.
Otherwise, all inputs are valid.
* Error message keys: ``required``, ``max_length``, ``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
@ -1177,6 +1203,7 @@ given length.
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* 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
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.
* Validates that the given value is either a ``datetime.date``,
``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
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.
* Validates that the given value is either a ``datetime.datetime``,
``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
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``.
* Validates that the given value is a decimal. Leading and trailing
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``,
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.
* Validates that the given value is a valid e-mail address, using a
moderately complex regular expression.
* Error message keys: ``required``, ``invalid``
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
@ -1269,6 +1302,7 @@ given length.
* Normalizes to: An ``UploadedFile`` object that wraps the file content
and file name into a single object.
* 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:
@ -1299,6 +1333,8 @@ When you use a ``FileField`` on a form, you must also remember to
and file name into a single object.
* Validates that file data has been bound to the form, and that the
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.
@ -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.
* Validates that the given value is an integer. Leading and trailing
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``.
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.
* Validates that the given value is a valid IPv4 address, using a regular
expression.
* Error message keys: ``required``, ``invalid``
``MultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~
@ -1336,6 +1375,7 @@ These control the range of values permitted in the field.
* Normalizes to: A list of Unicode objects.
* Validates that every value in the given list of values exists in the list
of choices.
* Error message keys: ``required``, ``invalid_choice``, ``invalid_list``
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.
@ -1356,6 +1396,7 @@ tuple) of 2-tuples to use as choices for this field.
* Normalizes to: A Unicode object.
* Validates that the given value matches against a certain regular
expression.
* Error message keys: ``required``, ``invalid``
Takes one required argument, ``regex``, which is a regular expression specified
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.
``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``
~~~~~~~~~~~~~
@ -1380,6 +1423,7 @@ Also takes the following optional arguments:
* Normalizes to: A Python ``datetime.time`` object.
* Validates that the given value is either a ``datetime.time`` or string
formatted in a particular time format.
* Error message keys: ``required``, ``invalid``
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.
@ -1396,6 +1440,7 @@ If no ``input_formats`` argument is provided, the default input formats are::
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value is a valid URL.
* Error message keys: ``required``, ``invalid``, ``invalid_link``
Takes the following optional arguments:

View File

@ -135,6 +135,23 @@ For example::
json_serializer = serializers.get_serializer("json")()
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
``````````````````````````

View File

@ -740,7 +740,7 @@ Available format strings:
if they're zero and the special-case
strings 'midnight' and 'noon' if
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 English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'``
month, 2 characters.
@ -1106,25 +1106,39 @@ floatformat
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:
* ``36.123`` gets converted to ``36.1``
* ``36.15`` gets converted to ``36.2``
* ``36`` gets converted to ``36``
======== ======================= ======
value Template Output
======== ======================= ======
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
many decimal places. For example:
If used with a numeric integer argument, ``floatformat`` rounds a number to
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
that many decimal places -- but only if there's a decimal part to be displayed.
For example:
If the argument passed to ``floatformat`` is negative, it will round a number
to that many decimal places -- but only if there's a decimal part to be
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
an argument of ``-1``.
Using ``floatformat`` with no argument is equivalent to using ``floatformat``
with an argument of ``-1``.
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::
from django.test import TestCase
from django.test.client import Client
class SimpleTest(TestCase):
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'
>>> floatformat(6.000000,3)
u'6.000'
>>> floatformat(6.200000, 3)
u'6.200'
>>> floatformat(6.200000, -3)
u'6.200'
>>> floatformat(13.1031,-3)
u'13.103'
>>> 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 -*-
tests = r"""
>>> from django.newforms import *
>>> from django.utils.encoding import force_unicode
>>> import datetime
>>> import time
>>> import re
@ -362,7 +363,7 @@ u'sirrobin'
... return self.as_divs()
... def as_divs(self):
... if not self: return u''
... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % e for e in self])
... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % force_unicode(e) for e in self])
>>> class CommentForm(Form):
... name = CharField(max_length=50, required=False)
... 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
early and still send back the right translation.
# XFAIL
>>> activate('de')
>>> print f.as_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 fields import tests as fields_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.au import tests as localflavor_au_tests
from localflavor.br import tests as localflavor_br_tests
@ -31,6 +32,7 @@ __test__ = {
'extra_tests': extra_tests,
'fields_tests': fields_tests,
'form_tests': form_tests,
'custom_error_message_tests': custom_error_message_tests,
'localflavor_ar_tests': localflavor_ar_tests,
'localflavor_au_tests': localflavor_au_tests,
'localflavor_br_tests': localflavor_br_tests,

View File

@ -42,4 +42,11 @@ u''
# Can take a mixture in a list.
>>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
>>> 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
(r'^$', views.index),
(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"),
# Unicode strings are permitted everywhere.

View File

@ -27,6 +27,14 @@ u'Paris+%26+Orl%C3%A9ans'
>>> urlquote_plus(u'Paris & Orl\xe9ans', safe="&")
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 ###########################################################
>>> from django.utils.encoding import iri_to_uri
>>> iri_to_uri(u'red%09ros\xe9#red')