mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
newforms-admin: Merged to [6652]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6656 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
6f15904669
commit
e29358827d
Binary file not shown.
@ -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
|
||||
|
@ -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."
|
||||
|
@ -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'
|
||||
@ -10,8 +10,9 @@ TEST_COOKIE_VALUE = 'worked'
|
||||
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))
|
||||
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||
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,
|
||||
|
@ -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
|
||||
|
@ -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__()
|
||||
|
@ -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,' % \
|
||||
(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,' % \
|
||||
(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' % \
|
||||
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,' %
|
||||
(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()))
|
||||
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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
53
django/db/models/fields/subclassing.py
Normal file
53
django/db/models/fields/subclassing.py
Normal 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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 ``&`` entities"
|
||||
"""Replaces ampersands with ``&`` 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)
|
||||
|
@ -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',))
|
||||
|
@ -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))
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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,13 +44,16 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
|
||||
"""
|
||||
if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)):
|
||||
return s
|
||||
if not isinstance(s, basestring,):
|
||||
if hasattr(s, '__unicode__'):
|
||||
s = unicode(s)
|
||||
else:
|
||||
s = unicode(str(s), encoding, errors)
|
||||
elif not isinstance(s, unicode):
|
||||
s = unicode(s, encoding, errors)
|
||||
try:
|
||||
if not isinstance(s, basestring,):
|
||||
if hasattr(s, '__unicode__'):
|
||||
s = unicode(s)
|
||||
else:
|
||||
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'):
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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 %}
|
||||
|
567
docs/custom_model_fields.txt
Normal file
567
docs/custom_model_fields.txt
Normal 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.
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
============
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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
|
||||
``````````````````````````
|
||||
|
||||
|
@ -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
|
||||
~~~~~~~~~
|
||||
|
@ -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):
|
||||
|
0
tests/modeltests/field_subclassing/__init__.py
Normal file
0
tests/modeltests/field_subclassing/__init__.py
Normal file
106
tests/modeltests/field_subclassing/models.py
Normal file
106
tests/modeltests/field_subclassing/models.py
Normal 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
|
||||
"""}
|
@ -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)
|
||||
|
315
tests/regressiontests/forms/error_messages.py
Normal file
315
tests/regressiontests/forms/error_messages.py
Normal 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']
|
||||
"""
|
@ -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()
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
"""
|
||||
|
@ -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.
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user