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

newforms-admin: Merged to [5243]. There are 3 failing tests in regressiontests.serializers_regress.tests.SerializerTests, but they fail in trunk also.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5244 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2007-05-15 03:37:41 +00:00
parent 415e84ad53
commit 4336591395
28 changed files with 3694 additions and 186 deletions

View File

@ -78,6 +78,7 @@ answer newbie questions, and generally made Django that much better:
flavio.curella@gmail.com flavio.curella@gmail.com
Jure Cuhalev <gandalf@owca.info> Jure Cuhalev <gandalf@owca.info>
dackze+django@gmail.com dackze+django@gmail.com
David Danier <goliath.mailinglist@gmx.de>
Dirk Datzert <dummy@habmalnefrage.de> Dirk Datzert <dummy@habmalnefrage.de>
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/> Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
dave@thebarproject.com dave@thebarproject.com
@ -85,6 +86,7 @@ answer newbie questions, and generally made Django that much better:
Alex Dedul Alex Dedul
deric@monowerks.com deric@monowerks.com
Max Derkachev <mderk@yandex.ru> Max Derkachev <mderk@yandex.ru>
Jordan Dimov <s3x3y1@gmail.com>
dne@mayonnaise.net dne@mayonnaise.net
Maximillian Dornseif <md@hudora.de> Maximillian Dornseif <md@hudora.de>
Jeremy Dunck <http://dunck.us/> Jeremy Dunck <http://dunck.us/>

View File

@ -38,6 +38,7 @@ LANGUAGE_CODE = 'en-us'
LANGUAGES = ( LANGUAGES = (
('ar', gettext_noop('Arabic')), ('ar', gettext_noop('Arabic')),
('bn', gettext_noop('Bengali')), ('bn', gettext_noop('Bengali')),
('bg', gettext_noop('Bulgarian')),
('ca', gettext_noop('Catalan')), ('ca', gettext_noop('Catalan')),
('cs', gettext_noop('Czech')), ('cs', gettext_noop('Czech')),
('cy', gettext_noop('Welsh')), ('cy', gettext_noop('Welsh')),

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,106 @@
# translation of djangojs.po to Bulgarian
#
msgid ""
msgstr ""
"Project-Id-Version: djangojs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2005-12-09 11:51+0100\n"
"PO-Revision-Date: 2007-05-12 17:51+0300\n"
"Last-Translator: Jordan Dimov <s3x3y1@gmail.com>\n"
"Language-Team: Bulgarian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: contrib/admin/media/js/SelectFilter2.js:33
#, perl-format
msgid "Available %s"
msgstr "Налични %s"
#: contrib/admin/media/js/SelectFilter2.js:41
msgid "Choose all"
msgstr "Избери всички"
#: contrib/admin/media/js/SelectFilter2.js:46
msgid "Add"
msgstr "Добави"
#: contrib/admin/media/js/SelectFilter2.js:48
msgid "Remove"
msgstr "Премахни"
#: contrib/admin/media/js/SelectFilter2.js:53
#, perl-format
msgid "Chosen %s"
msgstr "Избрахме %s"
#: contrib/admin/media/js/SelectFilter2.js:54
msgid "Select your choice(s) and click "
msgstr "Направете своя избор и щракнете "
#: contrib/admin/media/js/SelectFilter2.js:59
msgid "Clear all"
msgstr "Изчисти всички"
#: contrib/admin/media/js/dateparse.js:26
#: contrib/admin/media/js/calendar.js:24
msgid ""
"January February March April May June July August September October November "
"December"
msgstr "Януари Февруари Март Април Май Юни Юли Август Септември Октомври Ноември Декември"
#: contrib/admin/media/js/dateparse.js:27
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "Неделя Понеделник Вторник Сряда Четвъртък Петък Събота"
#: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S"
msgstr "Н П В С Ч П С"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
msgid "Now"
msgstr "Сега"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
msgid "Clock"
msgstr "Часовник"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77
msgid "Choose a time"
msgstr "Избери време"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
msgid "Midnight"
msgstr "Полунощ"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
msgid "6 a.m."
msgstr "6 a.m."
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
msgid "Noon"
msgstr "По обяд"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
msgid "Cancel"
msgstr "Отказ"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162
msgid "Today"
msgstr "Днес"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114
msgid "Calendar"
msgstr "Календар"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
msgid "Yesterday"
msgstr "Вчера"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
msgid "Tomorrow"
msgstr "Утре"

View File

@ -24,7 +24,7 @@ Usage
Subclass FormPreview and define a done() method: Subclass FormPreview and define a done() method:
def done(self, request, clean_data): def done(self, request, cleaned_data):
# ... # ...
This method takes an HttpRequest object and a dictionary of the form data after This method takes an HttpRequest object and a dictionary of the form data after
@ -113,7 +113,7 @@ class FormPreview(object):
if f.is_valid(): if f.is_valid():
if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')): if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
return self.failed_hash(request) # Security hash failed. return self.failed_hash(request) # Security hash failed.
return self.done(request, f.clean_data) return self.done(request, f.cleaned_data)
else: else:
return render_to_response(self.form_template, return render_to_response(self.form_template,
{'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
@ -160,6 +160,9 @@ class FormPreview(object):
# METHODS SUBCLASSES MUST OVERRIDE ######################################## # METHODS SUBCLASSES MUST OVERRIDE ########################################
def done(self, request, clean_data): def done(self, request, cleaned_data):
"Does something with the clean_data and returns an HttpResponseRedirect." """
Does something with the cleaned_data and returns an
HttpResponseRedirect.
"""
raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__) raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)

View File

@ -41,7 +41,7 @@ class ISIdNumberField(RegexField):
method is modulo 11. method is modulo 11.
""" """
check = [3, 2, 7, 6, 5, 4, 3, 2, 1, 0] check = [3, 2, 7, 6, 5, 4, 3, 2, 1, 0]
return sum(int(value[i]) * check[i] for i in range(10)) % 11 == 0 return sum([int(value[i]) * check[i] for i in range(10)]) % 11 == 0
def _format(self, value): def _format(self, value):
""" """

View File

@ -1407,21 +1407,25 @@ def load_data(fixture_labels, verbosity=1):
if verbosity > 1: if verbosity > 1:
print "No %s fixture '%s' in %s." % \ print "No %s fixture '%s' in %s." % \
(format, fixture_name, humanize(fixture_dir)) (format, fixture_name, humanize(fixture_dir))
if count[0] == 0:
if verbosity > 0: if count[0] > 0:
print "No fixtures found."
else:
if verbosity > 0:
print "Installed %d object(s) from %d fixture(s)" % tuple(count)
sequence_sql = backend.get_sql_sequence_reset(style, models) sequence_sql = backend.get_sql_sequence_reset(style, models)
if sequence_sql: if sequence_sql:
if verbosity > 1: if verbosity > 1:
print "Resetting sequences" print "Resetting sequences"
for line in sequence_sql: for line in sequence_sql:
cursor.execute(line) cursor.execute(line)
transaction.commit() transaction.commit()
transaction.leave_transaction_management() transaction.leave_transaction_management()
if count[0] == 0:
if verbosity > 0:
print "No fixtures found."
else:
if verbosity > 0:
print "Installed %d object(s) from %d fixture(s)" % tuple(count)
load_data.help_doc = 'Installs the named fixture(s) in the database' load_data.help_doc = 'Installs the named fixture(s) in the database'
load_data.args = "[--verbosity] fixture, fixture, ..." load_data.args = "[--verbosity] fixture, fixture, ..."

View File

@ -37,7 +37,7 @@ class Serializer(base.Serializer):
def handle_fk_field(self, obj, field): def handle_fk_field(self, obj, field):
related = getattr(obj, field.name) related = getattr(obj, field.name)
if related is not None: if related is not None:
related = related._get_pk_val() related = getattr(related, field.rel.field_name)
self._current[field.name] = related self._current[field.name] = related
def handle_m2m_field(self, obj, field): def handle_m2m_field(self, obj, field):
@ -80,7 +80,10 @@ def Deserializer(object_list, **options):
# Handle FK fields # Handle FK fields
elif field.rel and isinstance(field.rel, models.ManyToOneRel): elif field.rel and isinstance(field.rel, models.ManyToOneRel):
data[field.attname] = field.rel.to._meta.pk.to_python(field_value) if field_value:
data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
else:
data[field.attname] = None
# Handle all other fields # Handle all other fields
else: else:

View File

@ -82,7 +82,7 @@ class Serializer(base.Serializer):
self._start_relational_field(field) self._start_relational_field(field)
related = getattr(obj, field.name) related = getattr(obj, field.name)
if related is not None: if related is not None:
self.xml.characters(str(related._get_pk_val())) self.xml.characters(str(getattr(related, field.rel.field_name)))
else: else:
self.xml.addQuickElement("None") self.xml.addQuickElement("None")
self.xml.endElement("field") self.xml.endElement("field")
@ -181,7 +181,7 @@ class Deserializer(base.Deserializer):
if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None': if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None':
return None return None
else: else:
return field.rel.to._meta.pk.to_python( return field.rel.to._meta.get_field(field.rel.field_name).to_python(
getInnerText(node).strip().encode(self.encoding)) getInnerText(node).strip().encode(self.encoding))
def _handle_m2m_field_node(self, node, field): def _handle_m2m_field_node(self, node, field):

View File

@ -223,7 +223,7 @@ def get_sql_sequence_reset(style, model_list):
if isinstance(f, models.AutoField): if isinstance(f, models.AutoField):
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))),
style.SQL_KEYWORD('SELECT'), style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name(f.column)), style.SQL_FIELD(quote_name(f.column)),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
@ -232,7 +232,7 @@ def get_sql_sequence_reset(style, model_list):
for f in model._meta.many_to_many: for f in model._meta.many_to_many:
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())),
style.SQL_KEYWORD('SELECT'), style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('id')), style.SQL_FIELD(quote_name('id')),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),

View File

@ -180,7 +180,7 @@ def get_sql_sequence_reset(style, model_list):
if isinstance(f, models.AutoField): if isinstance(f, models.AutoField):
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))),
style.SQL_KEYWORD('SELECT'), style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name(f.column)), style.SQL_FIELD(quote_name(f.column)),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
@ -189,7 +189,7 @@ def get_sql_sequence_reset(style, model_list):
for f in model._meta.many_to_many: for f in model._meta.many_to_many:
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', (%s max(%s) %s %s));" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())),
style.SQL_KEYWORD('SELECT'), style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('id')), style.SQL_FIELD(quote_name('id')),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),

View File

@ -187,13 +187,13 @@ class BaseForm(StrAndUnicode):
def full_clean(self): def full_clean(self):
""" """
Cleans all of self.data and populates self.__errors and self.clean_data. Cleans all of self.data and populates self.__errors and self.cleaned_data.
""" """
errors = ErrorDict() errors = ErrorDict()
if not self.is_bound: # Stop further processing. if not self.is_bound: # Stop further processing.
self.__errors = errors self.__errors = errors
return return
self.clean_data = {} self.cleaned_data = {}
for name, field in self.fields.items(): for name, field in self.fields.items():
# value_from_datadict() gets the data from the dictionary. # value_from_datadict() gets the data from the dictionary.
# Each widget type knows how to retrieve its own data, because some # Each widget type knows how to retrieve its own data, because some
@ -201,18 +201,18 @@ class BaseForm(StrAndUnicode):
value = field.widget.value_from_datadict(self.data, self.add_prefix(name)) value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
try: try:
value = field.clean(value) value = field.clean(value)
self.clean_data[name] = value self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name): if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)() value = getattr(self, 'clean_%s' % name)()
self.clean_data[name] = value self.cleaned_data[name] = value
except ValidationError, e: except ValidationError, e:
errors[name] = e.messages errors[name] = e.messages
try: try:
self.clean_data = self.clean() self.cleaned_data = self.clean()
except ValidationError, e: except ValidationError, e:
errors[NON_FIELD_ERRORS] = e.messages errors[NON_FIELD_ERRORS] = e.messages
if errors: if errors:
delattr(self, 'clean_data') delattr(self, 'cleaned_data')
self.__errors = errors self.__errors = errors
def clean(self): def clean(self):
@ -222,7 +222,7 @@ class BaseForm(StrAndUnicode):
not be associated with a particular field; it will have a special-case not be associated with a particular field; it will have a special-case
association with the field named '__all__'. association with the field named '__all__'.
""" """
return self.clean_data return self.cleaned_data
class Form(BaseForm): class Form(BaseForm):
"A collection of Fields, plus their associated data." "A collection of Fields, plus their associated data."
@ -273,6 +273,8 @@ class BoundField(StrAndUnicode):
attrs['id'] = auto_id attrs['id'] = auto_id
if not self.form.is_bound: if not self.form.is_bound:
data = self.form.initial.get(self.name, self.field.initial) data = self.form.initial.get(self.name, self.field.initial)
if callable(data):
data = data()
else: else:
data = self.data data = self.data
return widget.render(self.html_name, data, attrs=attrs) return widget.render(self.html_name, data, attrs=attrs)

View File

@ -33,7 +33,7 @@ class BaseFormSet(object):
if data: if data:
self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix) self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix)
if self.management_form.is_valid(): if self.management_form.is_valid():
self.total_forms = self.management_form.clean_data[FORM_COUNT_FIELD_NAME] self.total_forms = self.management_form.cleaned_data[FORM_COUNT_FIELD_NAME]
self.required_forms = self.total_forms - self.num_extra self.required_forms = self.total_forms - self.num_extra
else: else:
# not sure that ValidationError is the best thing to raise here # not sure that ValidationError is the best thing to raise here
@ -67,7 +67,7 @@ class BaseFormSet(object):
def full_clean(self): def full_clean(self):
""" """
Cleans all of self.data and populates self.__errors and self.clean_data. Cleans all of self.data and populates self.__errors and self.cleaned_data.
""" """
is_valid = True is_valid = True
@ -75,7 +75,7 @@ class BaseFormSet(object):
if not self.is_bound: # Stop further processing. if not self.is_bound: # Stop further processing.
self.__errors = errors self.__errors = errors
return return
clean_data = [] cleaned_data = []
deleted_data = [] deleted_data = []
self._form_list = [] self._form_list = []
@ -103,12 +103,12 @@ class BaseFormSet(object):
self.add_fields(form, i) self.add_fields(form, i)
else: else:
# if the formset is still vaild overall and this form instance # if the formset is still vaild overall and this form instance
# is valid, keep appending to clean_data # is valid, keep appending to cleaned_data
if is_valid and form.is_valid(): if is_valid and form.is_valid():
if self.deletable and form.clean_data[DELETION_FIELD_NAME]: if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
deleted_data.append(form.clean_data) deleted_data.append(form.cleaned_data)
else: else:
clean_data.append(form.clean_data) cleaned_data.append(form.cleaned_data)
else: else:
is_valid = False is_valid = False
# append to errors regardless # append to errors regardless
@ -117,14 +117,14 @@ class BaseFormSet(object):
deleted_data.reverse() deleted_data.reverse()
if self.orderable: if self.orderable:
clean_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME]) cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
else: else:
clean_data.reverse() cleaned_data.reverse()
errors.reverse() errors.reverse()
self._form_list.reverse() self._form_list.reverse()
if is_valid: if is_valid:
self.clean_data = clean_data self.cleaned_data = cleaned_data
self.deleted_data = deleted_data self.deleted_data = deleted_data
self.errors = errors self.errors = errors
self._is_valid = is_valid self._is_valid = is_valid

View File

@ -12,19 +12,9 @@ from widgets import Select, SelectMultiple, MultipleHiddenInput
__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', __all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
'ModelChoiceField', 'ModelMultipleChoiceField') 'ModelChoiceField', 'ModelMultipleChoiceField')
def model_save(self, commit=True): def save_instance(form, instance, fields=None, fail_message='saved', commit=True):
""" """
Creates and returns model instance according to self.clean_data. Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
This method is created for any form_for_model Form.
"""
if self.errors:
raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
return save_instance(self, self._model(), commit)
def save_instance(form, instance, commit=True):
"""
Saves bound Form ``form``'s clean_data into model instance ``instance``.
Assumes ``form`` has a field for every non-AutoField database field in Assumes ``form`` has a field for every non-AutoField database field in
``instance``. If commit=True, then the changes to ``instance`` will be ``instance``. If commit=True, then the changes to ``instance`` will be
@ -33,30 +23,40 @@ def save_instance(form, instance, commit=True):
from django.db import models from django.db import models
opts = instance.__class__._meta opts = instance.__class__._meta
if form.errors: if form.errors:
raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name) raise ValueError("The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message))
clean_data = form.clean_data cleaned_data = form.cleaned_data
for f in opts.fields: for f in opts.fields:
if not f.editable or isinstance(f, models.AutoField) or not f.name in clean_data: if not f.editable or isinstance(f, models.AutoField) or not f.name in cleaned_data:
continue continue
setattr(instance, f.name, clean_data[f.name]) if fields and f.name not in fields:
continue
setattr(instance, f.name, cleaned_data[f.name])
if commit: if commit:
instance.save() instance.save()
for f in opts.many_to_many: for f in opts.many_to_many:
if f.name in clean_data: if fields and f.name not in fields:
setattr(instance, f.attname, clean_data[f.name]) continue
if f.name in cleaned_data:
setattr(instance, f.attname, cleaned_data[f.name])
# GOTCHA: If many-to-many data is given and commit=False, the many-to-many # GOTCHA: If many-to-many data is given and commit=False, the many-to-many
# data will be lost. This happens because a many-to-many options cannot be # data will be lost. This happens because a many-to-many options cannot be
# set on an object until after it's saved. Maybe we should raise an # set on an object until after it's saved. Maybe we should raise an
# exception in that case. # exception in that case.
return instance return instance
def make_instance_save(instance): def make_model_save(model, fields, fail_message):
"Returns the save() method for a form_for_instance Form." "Returns the save() method for a Form."
def save(self, commit=True): def save(self, commit=True):
return save_instance(self, instance, commit) return save_instance(self, model(), fields, fail_message, commit)
return save return save
def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield()): def make_instance_save(instance, fields, fail_message):
"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()):
""" """
Returns a Form class for the given Django model class. Returns a Form class for the given Django model class.
@ -71,13 +71,16 @@ def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfiel
for f in opts.fields + opts.many_to_many: for f in opts.fields + opts.many_to_many:
if not f.editable: if not f.editable:
continue continue
if fields and not f.name in fields:
continue
formfield = formfield_callback(f) formfield = formfield_callback(f)
if formfield: if formfield:
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
fields = SortedDictFromList(field_list) base_fields = SortedDictFromList(field_list)
return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save}) return type(opts.object_name + 'Form', (form,),
{'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')})
def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
""" """
Returns a Form class for the given Django model instance. Returns a Form class for the given Django model instance.
@ -94,13 +97,15 @@ def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kw
for f in opts.fields + opts.many_to_many: for f in opts.fields + opts.many_to_many:
if not f.editable: if not f.editable:
continue continue
if fields and not f.name in fields:
continue
current_value = f.value_from_object(instance) current_value = f.value_from_object(instance)
formfield = formfield_callback(f, initial=current_value) formfield = formfield_callback(f, initial=current_value)
if formfield: if formfield:
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
fields = SortedDictFromList(field_list) base_fields = SortedDictFromList(field_list)
return type(opts.object_name + 'InstanceForm', (form,), return type(opts.object_name + 'InstanceForm', (form,),
{'base_fields': fields, '_model': model, 'save': make_instance_save(instance)}) {'base_fields': base_fields, '_model': model, 'save': make_instance_save(instance, fields, 'changed')})
def form_for_fields(field_list): def form_for_fields(field_list):
"Returns a Form class for the given list of Django database field instances." "Returns a Form class for the given list of Django database field instances."

View File

@ -46,15 +46,15 @@ class TestCase(unittest.TestCase):
management.load_data(self.fixtures, verbosity=0) management.load_data(self.fixtures, verbosity=0)
mail.outbox = [] mail.outbox = []
def run(self, result=None): def __call__(self, result=None):
"""Wrapper around default run method to perform common Django test set up. """
This means that user-defined Test Cases aren't required to include a call Wrapper around default __call__ method to perform common Django test
to super().setUp(). set up. This means that user-defined Test Cases aren't required to
include a call to super().setUp().
""" """
self.client = Client() self.client = Client()
self._pre_setup() self._pre_setup()
super(TestCase, self).run(result) super(TestCase, self).__call__(result)
def assertRedirects(self, response, expected_path, status_code=302, target_status_code=200): def assertRedirects(self, response, expected_path, status_code=302, target_status_code=200):
"""Assert that a response redirected to a specific URL, and that the """Assert that a response redirected to a specific URL, and that the
@ -62,7 +62,7 @@ class TestCase(unittest.TestCase):
""" """
self.assertEqual(response.status_code, status_code, self.assertEqual(response.status_code, status_code,
"Response didn't redirect: Reponse code was %d (expected %d)" % "Response didn't redirect as expected: Reponse code was %d (expected %d)" %
(response.status_code, status_code)) (response.status_code, status_code))
scheme, netloc, path, params, query, fragment = urlparse(response['Location']) scheme, netloc, path, params, query, fragment = urlparse(response['Location'])
self.assertEqual(path, expected_path, self.assertEqual(path, expected_path,
@ -70,7 +70,7 @@ class TestCase(unittest.TestCase):
redirect_response = self.client.get(path) redirect_response = self.client.get(path)
self.assertEqual(redirect_response.status_code, target_status_code, self.assertEqual(redirect_response.status_code, target_status_code,
"Couldn't retrieve redirection page '%s': response code was %d (expected %d)" % "Couldn't retrieve redirection page '%s': response code was %d (expected %d)" %
(path, response.status_code, status_code)) (path, redirect_response.status_code, target_status_code))
def assertContains(self, response, text, count=1, status_code=200): def assertContains(self, response, text, count=1, status_code=200):
"""Assert that a response indicates that a page was retreived successfully, """Assert that a response indicates that a page was retreived successfully,
@ -108,7 +108,7 @@ class TestCase(unittest.TestCase):
for err in errors: for err in errors:
if field: if field:
if field in context[form].errors: if field in context[form].errors:
self.assertTrue(err in context[form].errors[field], self.failUnless(err in context[form].errors[field],
"The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" % "The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" %
(field, form, i, err, list(context[form].errors[field]))) (field, form, i, err, list(context[form].errors[field])))
elif field in context[form].fields: elif field in context[form].fields:
@ -117,7 +117,7 @@ class TestCase(unittest.TestCase):
else: else:
self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field)) self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field))
else: else:
self.assertTrue(err in context[form].non_field_errors(), self.failUnless(err in context[form].non_field_errors(),
"The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" % "The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" %
(form, i, err, list(context[form].non_field_errors()))) (form, i, err, list(context[form].non_field_errors())))
if not found_form: if not found_form:
@ -127,7 +127,7 @@ class TestCase(unittest.TestCase):
"Assert that the template with the provided name was used in rendering the response" "Assert that the template with the provided name was used in rendering the response"
if isinstance(response.template, list): if isinstance(response.template, list):
template_names = [t.name for t in response.template] template_names = [t.name for t in response.template]
self.assertTrue(template_name in template_names, self.failUnless(template_name in template_names,
"Template '%s' was not one of the templates used to render the response. Templates used: %s" % "Template '%s' was not one of the templates used to render the response. Templates used: %s" %
(template_name, template_names)) (template_name, template_names))
elif response.template: elif response.template:
@ -140,7 +140,7 @@ class TestCase(unittest.TestCase):
def assertTemplateNotUsed(self, response, template_name): def assertTemplateNotUsed(self, response, template_name):
"Assert that the template with the provided name was NOT used in rendering the response" "Assert that the template with the provided name was NOT used in rendering the response"
if isinstance(response.template, list): if isinstance(response.template, list):
self.assertFalse(template_name in [t.name for t in response.template], self.failIf(template_name in [t.name for t in response.template],
"Template '%s' was used unexpectedly in rendering the response" % template_name) "Template '%s' was used unexpectedly in rendering the response" % template_name)
elif response.template: elif response.template:
self.assertNotEqual(template_name, response.template.name, self.assertNotEqual(template_name, response.template.name,

View File

@ -44,7 +44,11 @@ How to use Databrowse
It doesn't matter where you put this, as long as it gets executed at It doesn't matter where you put this, as long as it gets executed at
some point. A good place for it is in your URLconf file (``urls.py``). some point. A good place for it is in your URLconf file (``urls.py``).
3. Add the following line to your URLconf:: 3. Change your URLconf to import the ``databrowse`` module::
from django.contrib import databrowse
...and add the following line to your URLconf::
(r'^databrowse/(.*)', databrowse.site.root), (r'^databrowse/(.*)', databrowse.site.root),

View File

@ -236,7 +236,7 @@ To pluralize, specify both the singular and plural forms with the
``{% plural %}`` tag, which appears within ``{% blocktrans %}`` and ``{% plural %}`` tag, which appears within ``{% blocktrans %}`` and
``{% endblocktrans %}``. Example:: ``{% endblocktrans %}``. Example::
{% blocktrans count list|count as counter %} {% blocktrans count list|length as counter %}
There is only one {{ name }} object. There is only one {{ name }} object.
{% plural %} {% plural %}
There are {{ counter }} {{ name }} objects. There are {{ counter }} {{ name }} objects.

View File

@ -9,28 +9,30 @@ framework. This document explains how to use this new library.
Migration plan Migration plan
============== ==============
``django.newforms`` currently is only available in Django beginning ``django.newforms`` is new in Django's 0.96 release, but, as it won't be new
with the 0.96 release. the Django development version -- i.e., it's forever, we plan to rename it to ``django.forms`` in the future. The current
not available in the Django 0.95 release. For the next Django release, ``django.forms`` package will be available as ``django.oldforms`` until Django
our plan is to do the following: 1.0, when we plan to remove it for good.
* As of revision [4208], we've copied the current ``django.forms`` to That has direct repercussions on the forward compatibility of your code. Please
``django.oldforms``. This allows you to upgrade your code *now* rather read the following migration plan and code accordingly:
than waiting for the backwards-incompatible change and rushing to fix
your code after the fact. Just change your import statements like this:: * The old forms framework (the current ``django.forms``) has been copied to
``django.oldforms``. Thus, you can start upgrading your code *now*,
rather than waiting for the future backwards-incompatible change, by
changing your import statements like this::
from django import forms # old from django import forms # old
from django import oldforms as forms # new from django import oldforms as forms # new
* At an undecided future date, we will move the current ``django.newforms`` * In the next Django release (0.97), we will move the current
to ``django.forms``. This will be a backwards-incompatible change, and ``django.newforms`` to ``django.forms``. This will be a
anybody who is still using the old version of ``django.forms`` at that backwards-incompatible change, and anybody who is still using the old
time will need to change their import statements, as described in the version of ``django.forms`` at that time will need to change their import
previous bullet. statements, as described in the previous bullet.
* We will remove ``django.oldforms`` in the release *after* the next Django * We will remove ``django.oldforms`` in the release *after* the next Django
release -- the release that comes after the release in which we're release -- either 0.98 or 1.0, whichever comes first.
creating the new ``django.forms``.
With this in mind, we recommend you use the following import statement when With this in mind, we recommend you use the following import statement when
using ``django.newforms``:: using ``django.newforms``::
@ -184,7 +186,7 @@ e-mail address::
>>> f.is_valid() >>> f.is_valid()
False False
Access the ``Form`` attribute ``errors`` to get a dictionary of error messages:: Access the ``errors`` attribute to get a dictionary of error messages::
>>> f.errors >>> f.errors
{'sender': [u'Enter a valid e-mail address.'], 'subject': [u'This field is required.']} {'sender': [u'Enter a valid e-mail address.'], 'subject': [u'This field is required.']}
@ -197,6 +199,10 @@ You can access ``errors`` without having to call ``is_valid()`` first. The
form's data will be validated the first time either you call ``is_valid()`` or form's data will be validated the first time either you call ``is_valid()`` or
access ``errors``. access ``errors``.
The validation routines will only get called once, regardless of how many times
you access ``errors`` or call ``is_valid()``. This means that if validation has
side effects, those side effects will only be triggered once.
Behavior of unbound forms Behavior of unbound forms
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
@ -224,7 +230,7 @@ object. Regardless of whether you pass it a string in the format
it's valid. it's valid.
Once you've created a ``Form`` instance with a set of data and validated it, Once you've created a ``Form`` instance with a set of data and validated it,
you can access the clean data via the ``clean_data`` attribute of the ``Form`` you can access the clean data via the ``cleaned_data`` attribute of the ``Form``
object:: object::
>>> data = {'subject': 'hello', >>> data = {'subject': 'hello',
@ -234,7 +240,7 @@ object::
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.clean_data >>> f.cleaned_data
{'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'} {'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
Note that any text-based field -- such as ``CharField`` or ``EmailField`` -- Note that any text-based field -- such as ``CharField`` or ``EmailField`` --
@ -242,7 +248,7 @@ always cleans the input into a Unicode string. We'll cover the encoding
implications later in this document. implications later in this document.
If your data does *not* validate, your ``Form`` instance will not have a If your data does *not* validate, your ``Form`` instance will not have a
``clean_data`` attribute:: ``cleaned_data`` attribute::
>>> data = {'subject': '', >>> data = {'subject': '',
... 'message': 'Hi there', ... 'message': 'Hi there',
@ -251,15 +257,15 @@ If your data does *not* validate, your ``Form`` instance will not have a
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
False False
>>> f.clean_data >>> f.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'ContactForm' object has no attribute 'clean_data' AttributeError: 'ContactForm' object has no attribute 'cleaned_data'
``clean_data`` will always *only* contain a key for fields defined in the ``cleaned_data`` will always *only* contain a key for fields defined in the
``Form``, even if you pass extra data when you define the ``Form``. In this ``Form``, even if you pass extra data when you define the ``Form``. In this
example, we pass a bunch of extra fields to the ``ContactForm`` constructor, example, we pass a bunch of extra fields to the ``ContactForm`` constructor,
but ``clean_data`` contains only the form's fields:: but ``cleaned_data`` contains only the form's fields::
>>> data = {'subject': 'hello', >>> data = {'subject': 'hello',
... 'message': 'Hi there', ... 'message': 'Hi there',
@ -271,9 +277,30 @@ but ``clean_data`` contains only the form's fields::
>>> f = ContactForm(data) >>> f = ContactForm(data)
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.clean_data # Doesn't contain extra_field_1, etc. >>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'} {'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
``cleaned_data`` will include a key and value for *all* fields defined in the
``Form``, even if the data didn't include a value for fields that are not
required. In this example, the data dictionary doesn't include a value for the
``nick_name`` field, but ``cleaned_data`` includes it, with an empty value::
>>> class OptionalPersonForm(Form):
... first_name = CharField()
... last_name = CharField()
... nick_name = CharField(required=False)
>>> data = {'first_name': u'John', 'last_name': u'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'}
In this above example, the ``cleaned_data`` value for ``nick_name`` is set to an
empty string, because ``nick_name`` is ``CharField``, and ``CharField``\s treat
empty values as an empty string. Each field type knows what its "blank" value
is -- e.g., for ``DateField``, it's ``None`` instead of the empty string.
Behavior of unbound forms Behavior of unbound forms
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
@ -281,10 +308,10 @@ It's meaningless to request "clean" data in a form with no data, but, for the
record, here's what happens with unbound forms:: record, here's what happens with unbound forms::
>>> f = ContactForm() >>> f = ContactForm()
>>> f.clean_data >>> f.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'ContactForm' object has no attribute 'clean_data' AttributeError: 'ContactForm' object has no attribute 'cleaned_data'
Outputting forms as HTML Outputting forms as HTML
------------------------ ------------------------
@ -454,7 +481,7 @@ field::
If ``auto_id`` is set to a string containing the format character ``'%s'``, If ``auto_id`` is set to a string containing the format character ``'%s'``,
then the form output will include ``<label>`` tags, and will generate ``id`` then the form output will include ``<label>`` tags, and will generate ``id``
attributes based on the format string. For example, for a format string attributes based on the format string. For example, for a format string
``'field_%s'``, a field named ``subject`` will get the ``id`` ``'field_%s'``, a field named ``subject`` will get the ``id`` value
``'field_subject'``. Continuing our example:: ``'field_subject'``. Continuing our example::
>>> f = ContactForm(auto_id='id_for_%s') >>> f = ContactForm(auto_id='id_for_%s')
@ -493,8 +520,9 @@ How errors are displayed
If you render a bound ``Form`` object, the act of rendering will automatically If you render a bound ``Form`` object, the act of rendering will automatically
run the form's validation if it hasn't already happened, and the HTML output run the form's validation if it hasn't already happened, and the HTML output
will include the validation errors as a ``<ul>`` near the field. The particular will include the validation errors as a ``<ul class="errorlist">`` near the
positioning of the error messages depends on the output method you're using:: field. The particular positioning of the error messages depends on the output
method you're using::
>>> data = {'subject': '', >>> data = {'subject': '',
... 'message': 'Hi there', ... 'message': 'Hi there',
@ -556,7 +584,8 @@ The field-specific output honors the form object's ``auto_id`` setting::
<input type="text" name="message" id="id_message" /> <input type="text" name="message" id="id_message" />
For a field's list of errors, access the field's ``errors`` attribute. This For a field's list of errors, access the field's ``errors`` attribute. This
is a list-like object that is displayed as an HTML ``<ul>`` when printed:: is a list-like object that is displayed as an HTML ``<ul class="errorlist">``
when printed::
>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''} >>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
>>> f = ContactForm(data, auto_id=False) >>> f = ContactForm(data, auto_id=False)
@ -646,7 +675,7 @@ Core field arguments
Each ``Field`` class constructor takes at least these arguments. Some Each ``Field`` class constructor takes at least these arguments. Some
``Field`` classes take additional, field-specific arguments, but the following ``Field`` classes take additional, field-specific arguments, but the following
should *always* be available: should *always* be accepted:
``required`` ``required``
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -704,7 +733,7 @@ field.)
The ``label`` argument lets you specify the "human-friendly" label for this The ``label`` argument lets you specify the "human-friendly" label for this
field. This is used when the ``Field`` is displayed in a ``Form``. field. This is used when the ``Field`` is displayed in a ``Form``.
As explained in _`Outputting forms as HTML` above, the default label for a As explained in "Outputting forms as HTML" above, the default label for a
``Field`` is generated from the field name by converting all underscores to ``Field`` is generated from the field name by converting all underscores to
spaces and upper-casing the first letter. Specify ``label`` if that default spaces and upper-casing the first letter. Specify ``label`` if that default
behavior doesn't result in an adequate label. behavior doesn't result in an adequate label.
@ -779,14 +808,15 @@ validation if a particular field's value is not given. ``initial`` values are
~~~~~~~~~~ ~~~~~~~~~~
The ``widget`` argument lets you specify a ``Widget`` class to use when The ``widget`` argument lets you specify a ``Widget`` class to use when
rendering this ``Field``. See _`Widgets` below for more information. rendering this ``Field``. See "Widgets" below for more information.
``help_text`` ``help_text``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
The ``help_text`` argument lets you specify descriptive text for this The ``help_text`` argument lets you specify descriptive text for this
``Field``. If you provide ``help_text``, it will be displayed next to the ``Field``. If you provide ``help_text``, it will be displayed next to the
``Field`` when the ``Field`` is rendered in a ``Form``. ``Field`` when the ``Field`` is rendered by one of the convenience ``Form``
methods (e.g., ``as_ul()``).
Here's a full example ``Form`` that implements ``help_text`` for two of its Here's a full example ``Form`` that implements ``help_text`` for two of its
fields. We've specified ``auto_id=False`` to simplify the output:: fields. We've specified ``auto_id=False`` to simplify the output::
@ -860,6 +890,212 @@ level and at the form instance level, and the latter gets precedence::
<tr><th>Url:</th><td><input type="text" name="url" /></td></tr> <tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
Built-in ``Field`` classes
--------------------------
Naturally, the ``newforms`` library comes with a set of ``Field`` classes that
represent common validation needs. This section documents each built-in field.
For each field, we describe the default widget used if you don't specify
``widget``. We also specify the value returned when you provide an empty value
(see the section on ``required`` above to understand what that means).
``BooleanField``
~~~~~~~~~~~~~~~~
* Default widget: ``CheckboxInput``
* Empty value: ``None``
* Normalizes to: A Python ``True`` or ``False`` value.
* Validates nothing (i.e., it never raises a ``ValidationError``).
``CharField``
~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates nothing, unless ``max_length`` or ``min_length`` is provided.
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
given length.
``ChoiceField``
~~~~~~~~~~~~~~~
* Default widget: ``Select``
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value exists in the list of choices.
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.
``DateField``
~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``None``
* 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.
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.
If no ``input_formats`` argument is provided, the default input formats are::
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
``DateTimeField``
~~~~~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``None``
* 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.
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.
If no ``input_formats`` argument is provided, the default input formats are::
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
'%m/%d/%y %H:%M', # '10/25/06 14:30'
'%m/%d/%y', # '10/25/06'
``EmailField``
~~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value is a valid e-mail address, using a
moderately complex regular expression.
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
given length.
``IntegerField``
~~~~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``None``
* 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.
``MultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~
* Default widget: ``SelectMultiple``
* Empty value: ``[]`` (an empty list)
* Normalizes to: A list of Unicode objects.
* Validates that every value in the given list of values exists in the list
of choices.
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.
``NullBooleanField``
~~~~~~~~~~~~~~~~~~~~
* Default widget: ``NullBooleanSelect``
* Empty value: ``None``
* Normalizes to: A Python ``True``, ``False`` or ``None`` value.
* Validates nothing (i.e., it never raises a ``ValidationError``).
``RegexField``
~~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value matches against a certain regular
expression.
Takes one required argument, ``regex``, which is a regular expression specified
either as a string or a compiled regular expression object.
Also takes the following optional arguments:
====================== =====================================================
Argument Description
====================== =====================================================
``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.
====================== =====================================================
``TimeField``
~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``None``
* 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.
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.
If no ``input_formats`` argument is provided, the default input formats are::
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
``URLField``
~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value is a valid URL.
Takes the following optional arguments:
======================== =====================================================
Argument Description
======================== =====================================================
``max_length`` Ensures the string has at most this many characters.
``min_length`` Ensures the string has at least this many characters.
``verify_exists`` If ``True``, the validator will attempt to load the
given URL, raising ``ValidationError`` if the page
gives a 404. Defaults to ``False``.
``validator_user_agent`` String used as the user-agent used when checking for
a URL's existence. Defaults to the value of the
``URL_VALIDATOR_USER_AGENT`` setting.
======================== =====================================================
Slightly complex built-in ``Field`` classes
-------------------------------------------
The following are not yet documented here. See the unit tests, linked-to from
the bottom of this document, for examples of their use.
``ComboField``
~~~~~~~~~~~~~~
``MultiValueField``
~~~~~~~~~~~~~~~~~~~
``SplitDateTimeField``
~~~~~~~~~~~~~~~~~~~~~~
Creating custom fields Creating custom fields
---------------------- ----------------------
@ -870,6 +1106,308 @@ custom ``Field`` classes. To do this, just create a subclass of
mentioned above (``required``, ``label``, ``initial``, ``widget``, mentioned above (``required``, ``label``, ``initial``, ``widget``,
``help_text``). ``help_text``).
Generating forms for models
===========================
If you're building a database-driven app, chances are you'll have forms that
map closely to Django models. For instance, you might have a ``BlogComment``
model, and you want to create a form that lets people submit comments. In this
case, it would be redundant to define the field types in your form, because
you've already defined the fields in your model.
For this reason, Django provides a few helper functions that let you create a
``Form`` class from a Django model.
``form_for_model()``
--------------------
The method ``django.newforms.form_for_model()`` creates a form based on the
definition of a specific model. Pass it the model class, and it will return a
``Form`` class that contains a form field for each model field.
For example::
>>> from django.newforms import form_for_model
# Create the form class.
>>> ArticleForm = form_for_model(Article)
# Create an empty form instance.
>>> f = ArticleForm()
It bears repeating that ``form_for_model()`` takes the model *class*, not a
model instance, and it returns a ``Form`` *class*, not a ``Form`` instance.
Field types
~~~~~~~~~~~
The generated ``Form`` class will have a form field for every model field. Each
model field has a corresponding default form field. For example, a
``CharField`` on a model is represented as a ``CharField`` on a form. A
model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
the full list of conversions:
=============================== ========================================
Model field Form field
=============================== ========================================
``AutoField`` Not represented in the form
``BooleanField`` ``BooleanField``
``CharField`` ``CharField`` with ``max_length`` set to
the model field's ``maxlength``
``CommaSeparatedIntegerField`` ``CharField``
``DateField`` ``DateField``
``DateTimeField`` ``DateTimeField``
``EmailField`` ``EmailField``
``FileField`` ``CharField``
``FilePathField`` ``CharField``
``FloatField`` ``CharField``
``ForeignKey`` ``ModelChoiceField`` (see below)
``ImageField`` ``CharField``
``IntegerField`` ``IntegerField``
``IPAddressField`` ``CharField``
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
below)
``NullBooleanField`` ``CharField``
``PhoneNumberField`` ``USPhoneNumberField``
(from ``django.contrib.localflavor.us``)
``PositiveIntegerField`` ``IntegerField``
``PositiveSmallIntegerField`` ``IntegerField``
``SlugField`` ``CharField``
``SmallIntegerField`` ``IntegerField``
``TextField`` ``CharField`` with ``widget=Textarea``
``TimeField`` ``TimeField``
``URLField`` ``URLField`` with ``verify_exists`` set
to the model field's ``verify_exists``
``USStateField`` ``CharField`` with
``widget=USStateSelect``
(``USStateSelect`` is from
``django.contrib.localflavor.us``)
``XMLField`` ``CharField`` with ``widget=Textarea``
=============================== ========================================
As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
types are special cases:
* ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
* ``ManyToManyField`` is represented by
``django.newforms.ModelMultipleChoiceField``, which is a
``MultipleChoiceField`` whose choices are a model ``QuerySet``.
In addition, each generated form field has attributes set as follows:
* If the model field has ``blank=True``, then ``required`` is set to
``False`` on the form field. Otherwise, ``required=True``.
* The form field's ``label`` is set to the ``verbose_name`` of the model
field, with the first character capitalized.
* The form field's ``help_text`` is set to the ``help_text`` of the model
field.
* If the model field has ``choices`` set, then the form field's ``widget``
will be set to ``Select``, with choices coming from the model field's
``choices``.
Finally, note that you can override the form field used for a given model
field. See "Overriding the default field types" below.
A full example
~~~~~~~~~~~~~~
Consider this set of models::
from django.db import models
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(maxlength=100)
title = models.CharField(maxlength=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(maxlength=100)
authors = models.ManyToManyField(Author)
With these models, a call to ``form_for_model(Author)`` would return a ``Form``
class equivalent to this::
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(max_length=3,
widget=forms.Select(choices=TITLE_CHOICES))
birth_date = forms.DateField(required=False)
A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to
this::
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
The ``save()`` method
~~~~~~~~~~~~~~~~~~~~~
Every form produced by ``form_for_model()`` also has a ``save()`` method. This
method creates and saves a database object from the data bound to the form. For
example::
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
Note that ``save()`` will raise a ``ValueError`` if the data in the form
doesn't validate -- i.e., ``if form.errors``.
Using an alternate base class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to add custom methods to the form generated by
``form_for_model()``, write a class that extends ``django.newforms.BaseForm``
and contains your custom methods. Then, use the ``form`` argument to
``form_for_model()`` to tell it to use your custom form as its base class.
For example::
# Create the new base class.
>>> class MyBase(BaseForm):
... def my_method(self):
... # Do whatever the method does
# Create the form class with a different base class.
>>> ArticleForm = form_for_model(Article, form=MyBase)
# Instantiate the form.
>>> f = ArticleForm()
# Use the base class method.
>>> f.my_method()
Using a subset of fields on the form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**New in Django development version**
In some cases, you may not want all the model fields to appear on the generated
form. There are two ways of telling ``form_for_model()`` to use only a subset
of the model fields:
1. Set ``editable=False`` on the model field. As a result, *any* form
created from the model via ``form_for_model()`` will not include that
field.
2. Use the ``fields`` argument to ``form_for_model()``. This argument, if
given, should be a list of field names to include in the form.
For example, if you want a form for the ``Author`` model (defined above)
that includes only the ``name`` and ``title`` fields, you would specify
``fields`` like this::
PartialArticleForm = form_for_model(Author, fields=('name', 'title'))
.. note::
If you specify ``fields`` when creating a form with ``form_for_model()``,
make sure that the fields that are *not* specified can provide default
values, or are allowed to have a value of ``None``. If a field isn't
specified on a form, the object created from the form can't provide
a value for that attribute, which will prevent the new instance from
being saved.
Overriding the default field types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default field types, as described in the "Field types" table above, are
sensible defaults; if you have a ``DateField`` in your model, chances are you'd
want that to be represented as a ``DateField`` in your form. But
``form_for_model()`` gives you the flexibility of changing the form field type
for a given model field. You do this by specifying a **formfield callback**.
A formfield callback is a function that, when provided with a model field,
returns a form field instance. When constructing a form, ``form_for_model()``
asks the formfield callback to provide form field types.
By default, ``form_for_model()`` calls the ``formfield()`` method on the model
field::
def default_callback(field, **kwargs):
return field.formfield(**kwargs)
The ``kwargs`` are any keyword arguments that might be passed to the form
field, such as ``required=True`` or ``label='Foo'``.
For example, if you wanted to use ``MyDateFormField`` for any ``DateField``
field on the model, you could define the callback::
>>> def my_callback(field, **kwargs):
... if isinstance(field, models.DateField):
... return MyDateFormField(**kwargs)
... else:
... return field.formfield(**kwargs)
>>> ArticleForm = form_for_model(formfield_callback=my_callback)
Note that your callback needs to handle *all* possible model field types, not
just the ones that you want to behave differently to the default. That's why
this example has an ``else`` clause that implements the default behavior.
Finding the model associated with a form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The model class that was used to construct the form is available
using the ``_model`` property of the generated form::
>>> ArticleForm = form_for_model(Article)
>>> ArticleForm._model
<class 'myapp.models.Article'>
``form_for_instance()``
-----------------------
``form_for_instance()`` is like ``form_for_model()``, but it takes a model
instance instead of a model class::
# Create an Author.
>>> a = Author(name='Joe Smith', title='MR', birth_date=None)
>>> a.save()
# Create a form for this particular Author.
>>> AuthorForm = form_for_instance(a)
# Instantiate the form.
>>> f = AuthorForm()
When a form created by ``form_for_instance()`` is created, the initial
data values for the form fields are drawn from the instance. However,
this data is not bound to the form. You will need to bind data to the
form before the form can be saved.
When you call ``save()`` on a form created by ``form_for_instance()``,
the database instance will be updated. As in ``form_for_model()``, ``save()``
will raise ``ValueError`` if the data doesn't validate.
``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback``
arguments that behave the same way as they do for ``form_for_model()``.
When should you use ``form_for_model()`` and ``form_for_instance()``?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be
shortcuts for the common case. If you want to create a form whose fields map to
more than one model, or a form that contains fields that *aren't* on a model,
you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
isn't that difficult, after all.
More coming soon More coming soon
================ ================
@ -880,6 +1418,3 @@ what's possible.
If you're really itching to learn and use this library, please be patient. If you're really itching to learn and use this library, please be patient.
We're working hard on finishing both the code and documentation. We're working hard on finishing both the code and documentation.
Widgets
=======

View File

@ -177,7 +177,7 @@ tools that can be used to establish tests and test conditions.
* `Test Client`_ * `Test Client`_
* `TestCase`_ * `TestCase`_
* `Email services`_ * `E-mail services`_
Test Client Test Client
----------- -----------
@ -459,9 +459,9 @@ Emptying the test outbox
**New in Django development version** **New in Django development version**
At the start of each test case, in addition to installing fixtures, At the start of each test case, in addition to installing fixtures,
Django clears the contents of the test email outbox. Django clears the contents of the test e-mail outbox.
For more detail on email services during tests, see `Email services`_. For more detail on e-mail services during tests, see `E-mail services`_.
Assertions Assertions
~~~~~~~~~~ ~~~~~~~~~~
@ -502,16 +502,17 @@ that can be useful in testing the behavior of web sites.
Assert that the template with the given name was used in rendering the Assert that the template with the given name was used in rendering the
response. response.
Email services E-mail services
-------------- ---------------
**New in Django development version** **New in Django development version**
If your view makes use of the `Django email services`_, you don't really If your view makes use of the `Django e-mail services`_, you don't really
want email to be sent every time you run a test using that view. want e-mail to be sent every time you run a test using that view.
When the Django test framework is initialized, it transparently replaces the When the Django test framework is initialized, it transparently replaces the
normal `SMTPConnection`_ class with a dummy implementation that redirects all normal `SMTPConnection`_ class with a dummy implementation that redirects all
email to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``, e-mail to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``,
is a simple list of all `EmailMessage`_ instances that have been sent. is a simple list of all `EmailMessage`_ instances that have been sent.
For example, during test conditions, it would be possible to run the following For example, during test conditions, it would be possible to run the following
code:: code::
@ -541,7 +542,7 @@ to mail.outbox::
# Empty the test outbox # Empty the test outbox
mail.outbox = [] mail.outbox = []
.. _`Django email services`: ../email/ .. _`Django e-mail services`: ../email/
.. _`SMTPConnection`: ../email/#the-emailmessage-and-smtpconnection-classes .. _`SMTPConnection`: ../email/#the-emailmessage-and-smtpconnection-classes
.. _`EmailMessage`: ../email/#the-emailmessage-and-smtpconnection-classes .. _`EmailMessage`: ../email/#the-emailmessage-and-smtpconnection-classes
.. _`previously`: #emptying-the-test-outbox .. _`previously`: #emptying-the-test-outbox
@ -669,7 +670,7 @@ a number of utility methods in the ``django.test.utils`` module.
``teardown_test_environment()`` ``teardown_test_environment()``
Performs any global post-test teardown, such as removing the instrumentation Performs any global post-test teardown, such as removing the instrumentation
of the template rendering system and restoring normal email services. of the template rendering system and restoring normal e-mail services.
``create_test_db(verbosity=1, autoclobber=False)`` ``create_test_db(verbosity=1, autoclobber=False)``
Creates a new test database, and run ``syncdb`` against it. Creates a new test database, and run ``syncdb`` against it.

View File

@ -18,7 +18,7 @@ other Form, with one additional method: save(). The save()
method updates the model instance. It also takes a commit=True parameter. method updates the model instance. It also takes a commit=True parameter.
The function django.newforms.save_instance() takes a bound form instance and a The function django.newforms.save_instance() takes a bound form instance and a
model instance and saves the form's clean_data into the instance. It also takes model instance and saves the form's cleaned_data into the instance. It also takes
a commit=True parameter. a commit=True parameter.
""" """
@ -94,7 +94,7 @@ __test__ = {'API_TESTS': """
>>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'}) >>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.clean_data >>> f.cleaned_data
{'url': u'entertainment', 'name': u'Entertainment'} {'url': u'entertainment', 'name': u'Entertainment'}
>>> obj = f.save() >>> obj = f.save()
>>> obj >>> obj
@ -105,7 +105,7 @@ True
>>> f = CategoryForm({'name': "It's a test", 'url': 'test'}) >>> f = CategoryForm({'name': "It's a test", 'url': 'test'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.clean_data >>> f.cleaned_data
{'url': u'test', 'name': u"It's a test"} {'url': u'test', 'name': u"It's a test"}
>>> obj = f.save() >>> obj = f.save()
>>> obj >>> obj
@ -119,7 +119,7 @@ save() on the resulting model instance.
>>> f = CategoryForm({'name': 'Third test', 'url': 'third'}) >>> f = CategoryForm({'name': 'Third test', 'url': 'third'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.clean_data >>> f.cleaned_data
{'url': u'third', 'name': u'Third test'} {'url': u'third', 'name': u'Third test'}
>>> obj = f.save(commit=False) >>> obj = f.save(commit=False)
>>> obj >>> obj
@ -134,10 +134,10 @@ If you call save() with invalid data, you'll get a ValueError.
>>> f = CategoryForm({'name': '', 'url': 'foo'}) >>> f = CategoryForm({'name': '', 'url': 'foo'})
>>> f.errors >>> f.errors
{'name': [u'This field is required.']} {'name': [u'This field is required.']}
>>> f.clean_data >>> f.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'CategoryForm' object has no attribute 'clean_data' AttributeError: 'CategoryForm' object has no attribute 'cleaned_data'
>>> f.save() >>> f.save()
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -179,6 +179,18 @@ fields with the 'choices' attribute are represented by a ChoiceField.
<option value="3">Third test</option> <option value="3">Third test</option>
</select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr> </select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>
You can restrict a form to a subset of the complete list of fields
by providing a 'fields' argument. If you try to save a
model created with such a form, you need to ensure that the fields
that are _not_ on the form have default values, or are allowed to have
a value of None. If a field isn't specified on a form, the object created
from the form can't provide a value for that field!
>>> PartialArticleForm = form_for_model(Article, fields=('headline','pub_date'))
>>> f = PartialArticleForm(auto_id=False)
>>> print f
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
You can pass a custom Form class to form_for_model. Make sure it's a You can pass a custom Form class to form_for_model. Make sure it's a
subclass of BaseForm, not Form. subclass of BaseForm, not Form.
>>> class CustomForm(BaseForm): >>> class CustomForm(BaseForm):
@ -224,7 +236,23 @@ current values are inserted as 'initial' data in each Field.
<option value="2">It&#39;s a test</option> <option value="2">It&#39;s a test</option>
<option value="3">Third test</option> <option value="3">Third test</option>
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
>>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1', 'article': 'Hello.'}) >>> f = TestArticleForm({'headline': u'Test headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'})
>>> f.is_valid()
True
>>> test_art = f.save()
>>> test_art.id
1
>>> test_art = Article.objects.get(id=1)
>>> test_art.headline
'Test headline'
You can create a form over a subset of the available fields
by specifying a 'fields' argument to form_for_instance.
>>> PartialArticleForm = form_for_instance(art, fields=('headline','pub_date'))
>>> f = PartialArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04'}, auto_id=False)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
>>> f.is_valid() >>> f.is_valid()
True True
>>> new_art = f.save() >>> new_art = f.save()
@ -496,6 +524,6 @@ ValidationError: [u'Select a valid choice. 10 is not one of the available choice
>>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) >>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.clean_data >>> f.cleaned_data
{'phone': u'312-555-1212', 'description': u'Assistance'} {'phone': u'312-555-1212', 'description': u'Assistance'}
"""} """}

View File

@ -38,14 +38,14 @@ the COUNT field appropriately.
... } ... }
We treat FormSet pretty much like we would treat a normal Form. FormSet has an We treat FormSet pretty much like we would treat a normal Form. FormSet has an
is_valid method, and a clean_data or errors attribute depending on whether all is_valid method, and a cleaned_data or errors attribute depending on whether all
the forms passed validation. However, unlike a Form instance, clean_data and the forms passed validation. However, unlike a Form instance, cleaned_data and
errors will be a list of dicts rather than just a single dict. errors will be a list of dicts rather than just a single dict.
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
>>> formset.is_valid() >>> formset.is_valid()
True True
>>> formset.clean_data >>> formset.cleaned_data
[{'votes': 100, 'choice': u'Calexico'}] [{'votes': 100, 'choice': u'Calexico'}]
@ -64,12 +64,12 @@ False
>>> formset.errors >>> formset.errors
[{'votes': [u'This field is required.']}] [{'votes': [u'This field is required.']}]
Like a Form instance, clean_data won't exist if the formset wasn't validated. Like a Form instance, cleaned_data won't exist if the formset wasn't validated.
>>> formset.clean_data >>> formset.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'ChoiceFormSet' object has no attribute 'clean_data' AttributeError: 'ChoiceFormSet' object has no attribute 'cleaned_data'
We can also prefill a FormSet with existing data by providing an ``initial`` We can also prefill a FormSet with existing data by providing an ``initial``
@ -99,7 +99,7 @@ Let's simulate what would happen if we submitted this form.
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
>>> formset.is_valid() >>> formset.is_valid()
True True
>>> formset.clean_data >>> formset.cleaned_data
[{'votes': 100, 'choice': u'Calexico'}] [{'votes': 100, 'choice': u'Calexico'}]
But the second form was blank! Shouldn't we get some errors? No. If we display But the second form was blank! Shouldn't we get some errors? No. If we display
@ -176,7 +176,7 @@ number of forms to be completed.
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
>>> formset.is_valid() >>> formset.is_valid()
True True
>>> formset.clean_data >>> formset.cleaned_data
[] []
@ -195,7 +195,7 @@ We can just fill out one of the forms.
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
>>> formset.is_valid() >>> formset.is_valid()
True True
>>> formset.clean_data >>> formset.cleaned_data
[{'votes': 100, 'choice': u'Calexico'}] [{'votes': 100, 'choice': u'Calexico'}]
@ -262,7 +262,7 @@ False
We can easily add deletion ability to a FormSet with an agrument to We can easily add deletion ability to a FormSet with an agrument to
formset_for_form. This will add a boolean field to each form instance. When formset_for_form. This will add a boolean field to each form instance. When
that boolean field is True, the cleaned data will be in formset.deleted_data that boolean field is True, the cleaned data will be in formset.deleted_data
rather than formset.clean_data rather than formset.cleaned_data
>>> ChoiceFormSet = formset_for_form(Choice, deletable=True) >>> ChoiceFormSet = formset_for_form(Choice, deletable=True)
@ -299,7 +299,7 @@ To delete something, we just need to set that form's special delete field to
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
>>> formset.is_valid() >>> formset.is_valid()
True True
>>> formset.clean_data >>> formset.cleaned_data
[{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}] [{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}]
>>> formset.deleted_data >>> formset.deleted_data
[{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}] [{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}]
@ -308,7 +308,7 @@ True
We can also add ordering ability to a FormSet with an agrument to We can also add ordering ability to a FormSet with an agrument to
formset_for_form. This will add a integer field to each form instance. When formset_for_form. This will add a integer field to each form instance. When
form validation succeeds, formset.clean_data will have the data in the correct form validation succeeds, formset.cleaned_data will have the data in the correct
order specified by the ordering fields. If a number is duplicated in the set order specified by the ordering fields. If a number is duplicated in the set
of ordering fields, for instance form 0 and form 3 are both marked as 1, then of ordering fields, for instance form 0 and form 3 are both marked as 1, then
the form index used as a secondary ordering criteria. In order to put the form index used as a secondary ordering criteria. In order to put
@ -346,8 +346,8 @@ something at the front of the list, you'd need to set it's order to 0.
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
>>> formset.is_valid() >>> formset.is_valid()
True True
>>> for clean_data in formset.clean_data: >>> for cleaned_data in formset.cleaned_data:
... print clean_data ... print cleaned_data
{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'} {'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'}
{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'} {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'} {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
@ -408,8 +408,8 @@ Let's delete Fergie, and put The Decemberists ahead of Calexico.
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
>>> formset.is_valid() >>> formset.is_valid()
True True
>>> for clean_data in formset.clean_data: >>> for cleaned_data in formset.cleaned_data:
... print clean_data ... print cleaned_data
{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'} {'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'}
{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'} {'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'}
>>> formset.deleted_data >>> formset.deleted_data

View File

@ -34,4 +34,18 @@ Unicode decoding problems...
>>> f = SomeForm() >>> f = SomeForm()
>>> f.as_p() >>> f.as_p()
u'<p><label for="id_somechoice_0">Somechoice:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="0" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="1" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="2" name="somechoice" /> Nainen</label></li>\n</ul></p>' u'<p><label for="id_somechoice_0">Somechoice:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="0" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="1" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="2" name="somechoice" /> Nainen</label></li>\n</ul></p>'
#######################
# Miscellaneous Tests #
#######################
There once was a problem with Form fields called "data". Let's make sure that
doesn't come back.
>>> class DataForm(Form):
... data = CharField(max_length=10)
>>> f = DataForm({'data': 'xyzzy'})
>>> f.is_valid()
True
>>> f.cleaned_data
{'data': u'xyzzy'}
""" """

View File

@ -1775,7 +1775,7 @@ True
u'' u''
>>> p.errors.as_text() >>> p.errors.as_text()
u'' u''
>>> p.clean_data >>> p.cleaned_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
>>> print p['first_name'] >>> print p['first_name']
<input type="text" name="first_name" value="John" id="id_first_name" /> <input type="text" name="first_name" value="John" id="id_first_name" />
@ -1811,10 +1811,10 @@ True
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']} {'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
>>> p.is_valid() >>> p.is_valid()
False False
>>> p.clean_data >>> p.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'Person' object has no attribute 'clean_data' AttributeError: 'Person' object has no attribute 'cleaned_data'
>>> print p >>> print p
<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr> <tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr> <tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
@ -1845,10 +1845,10 @@ False
{} {}
>>> p.is_valid() >>> p.is_valid()
False False
>>> p.clean_data >>> p.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'Person' object has no attribute 'clean_data' AttributeError: 'Person' object has no attribute 'cleaned_data'
>>> print p >>> print p
<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr> <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr> <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
@ -1887,10 +1887,10 @@ u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is re
* This field is required. * This field is required.
* birthday * birthday
* This field is required. * This field is required.
>>> p.clean_data >>> p.cleaned_data
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: 'Person' object has no attribute 'clean_data' AttributeError: 'Person' object has no attribute 'cleaned_data'
>>> p['first_name'].errors >>> p['first_name'].errors
[u'This field is required.'] [u'This field is required.']
>>> p['first_name'].errors.as_ul() >>> p['first_name'].errors.as_ul()
@ -1906,17 +1906,45 @@ u'* This field is required.'
>>> print p['birthday'] >>> print p['birthday']
<input type="text" name="birthday" id="id_birthday" /> <input type="text" name="birthday" id="id_birthday" />
clean_data will always *only* contain a key for fields defined in the cleaned_data will always *only* contain a key for fields defined in the
Form, even if you pass extra data when you define the Form. In this Form, even if you pass extra data when you define the Form. In this
example, we pass a bunch of extra fields to the form constructor, example, we pass a bunch of extra fields to the form constructor,
but clean_data contains only the form's fields. but cleaned_data contains only the form's fields.
>>> data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'} >>> data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'}
>>> p = Person(data) >>> p = Person(data)
>>> p.is_valid() >>> p.is_valid()
True True
>>> p.clean_data >>> p.cleaned_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
cleaned_data will include a key and value for *all* fields defined in the Form,
even if the Form's data didn't include a value for fields that are not
required. In this example, the data dictionary doesn't include a value for the
"nick_name" field, but cleaned_data includes it. For CharFields, it's set to the
empty string.
>>> class OptionalPersonForm(Form):
... first_name = CharField()
... last_name = CharField()
... nick_name = CharField(required=False)
>>> data = {'first_name': u'John', 'last_name': u'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'}
For DateFields, it's set to None.
>>> class OptionalPersonForm(Form):
... first_name = CharField()
... last_name = CharField()
... birth_date = DateField(required=False)
>>> data = {'first_name': u'John', 'last_name': u'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'birth_date': None, 'first_name': u'John', 'last_name': u'Lennon'}
"auto_id" tells the Form to add an "id" attribute to each form element. "auto_id" tells the Form to add an "id" attribute to each form element.
If it's a string that contains '%s', Django will use that as a format string If it's a string that contains '%s', Django will use that as a format string
into which the field's name will be inserted. It will also put a <label> around into which the field's name will be inserted. It will also put a <label> around
@ -2265,19 +2293,19 @@ returns a list of input.
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False) >>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.clean_data >>> f.cleaned_data
{'composers': [u'J'], 'name': u'Yesterday'} {'composers': [u'J'], 'name': u'Yesterday'}
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False) >>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.clean_data >>> f.cleaned_data
{'composers': [u'J', u'P'], 'name': u'Yesterday'} {'composers': [u'J', u'P'], 'name': u'Yesterday'}
Validation errors are HTML-escaped when output as HTML. Validation errors are HTML-escaped when output as HTML.
>>> class EscapingForm(Form): >>> class EscapingForm(Form):
... special_name = CharField() ... special_name = CharField()
... def clean_special_name(self): ... def clean_special_name(self):
... raise ValidationError("Something's wrong with '%s'" % self.clean_data['special_name']) ... raise ValidationError("Something's wrong with '%s'" % self.cleaned_data['special_name'])
>>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False) >>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False)
>>> print f >>> print f
@ -2292,7 +2320,7 @@ There are a couple of ways to do multiple-field validation. If you want the
validation message to be associated with a particular field, implement the validation message to be associated with a particular field, implement the
clean_XXX() method on the Form, where XXX is the field name. As in clean_XXX() method on the Form, where XXX is the field name. As in
Field.clean(), the clean_XXX() method should return the cleaned value. In the Field.clean(), the clean_XXX() method should return the cleaned value. In the
clean_XXX() method, you have access to self.clean_data, which is a dictionary clean_XXX() method, you have access to self.cleaned_data, which is a dictionary
of all the data that has been cleaned *so far*, in order by the fields, of all the data that has been cleaned *so far*, in order by the fields,
including the current field (e.g., the field XXX if you're in clean_XXX()). including the current field (e.g., the field XXX if you're in clean_XXX()).
>>> class UserRegistration(Form): >>> class UserRegistration(Form):
@ -2300,9 +2328,9 @@ including the current field (e.g., the field XXX if you're in clean_XXX()).
... password1 = CharField(widget=PasswordInput) ... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput) ... password2 = CharField(widget=PasswordInput)
... def clean_password2(self): ... def clean_password2(self):
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']: ... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.') ... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data['password2'] ... return self.cleaned_data['password2']
>>> f = UserRegistration(auto_id=False) >>> f = UserRegistration(auto_id=False)
>>> f.errors >>> f.errors
{} {}
@ -2315,14 +2343,14 @@ including the current field (e.g., the field XXX if you're in clean_XXX()).
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.clean_data >>> f.cleaned_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'} {'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
Another way of doing multiple-field validation is by implementing the Another way of doing multiple-field validation is by implementing the
Form's clean() method. If you do this, any ValidationError raised by that Form's clean() method. If you do this, any ValidationError raised by that
method will not be associated with a particular field; it will have a method will not be associated with a particular field; it will have a
special-case association with the field named '__all__'. special-case association with the field named '__all__'.
Note that in Form.clean(), you have access to self.clean_data, a dictionary of Note that in Form.clean(), you have access to self.cleaned_data, a dictionary of
all the fields/values that have *not* raised a ValidationError. Also note all the fields/values that have *not* raised a ValidationError. Also note
Form.clean() is required to return a dictionary of all clean data. Form.clean() is required to return a dictionary of all clean data.
>>> class UserRegistration(Form): >>> class UserRegistration(Form):
@ -2330,9 +2358,9 @@ Form.clean() is required to return a dictionary of all clean data.
... password1 = CharField(widget=PasswordInput) ... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput) ... password2 = CharField(widget=PasswordInput)
... def clean(self): ... def clean(self):
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']: ... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.') ... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data ... return self.cleaned_data
>>> f = UserRegistration(auto_id=False) >>> f = UserRegistration(auto_id=False)
>>> f.errors >>> f.errors
{} {}
@ -2359,7 +2387,7 @@ Form.clean() is required to return a dictionary of all clean data.
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
>>> f.clean_data >>> f.cleaned_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'} {'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
# Dynamic construction ######################################################## # Dynamic construction ########################################################
@ -2753,6 +2781,64 @@ then the latter will get precedence.
<li>Username: <input type="text" name="username" value="babik" maxlength="10" /></li> <li>Username: <input type="text" name="username" value="babik" maxlength="10" /></li>
<li>Password: <input type="password" name="password" /></li> <li>Password: <input type="password" name="password" /></li>
# Callable initial data ########################################################
The previous technique dealt with raw values as initial data, but it's also
possible to specify callable data.
>>> class UserRegistration(Form):
... username = CharField(max_length=10)
... password = CharField(widget=PasswordInput)
We need to define functions that get called later.
>>> def initial_django():
... return 'django'
>>> def initial_stephane():
... return 'stephane'
Here, we're not submitting any data, so the initial value will be displayed.
>>> p = UserRegistration(initial={'username': initial_django}, auto_id=False)
>>> print p.as_ul()
<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
<li>Password: <input type="password" name="password" /></li>
The 'initial' parameter is meaningless if you pass data.
>>> p = UserRegistration({}, initial={'username': initial_django}, auto_id=False)
>>> print p.as_ul()
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
>>> p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False)
>>> print p.as_ul()
<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
>>> p = UserRegistration({'username': u'foo'}, initial={'username': initial_django}, auto_id=False)
>>> print p.as_ul()
<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
A callable 'initial' value is *not* used as a fallback if data is not provided.
In this example, we don't provide a value for 'username', and the form raises a
validation error rather than using the initial value for 'username'.
>>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django})
>>> p.errors
{'username': [u'This field is required.']}
>>> p.is_valid()
False
If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(),
then the latter will get precedence.
>>> class UserRegistration(Form):
... username = CharField(max_length=10, initial=initial_django)
... password = CharField(widget=PasswordInput)
>>> p = UserRegistration(auto_id=False)
>>> print p.as_ul()
<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
<li>Password: <input type="password" name="password" /></li>
>>> p = UserRegistration(initial={'username': initial_stephane}, auto_id=False)
>>> print p.as_ul()
<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
<li>Password: <input type="password" name="password" /></li>
# Help text ################################################################### # Help text ###################################################################
You can specify descriptive text for a field by using the 'help_text' argument You can specify descriptive text for a field by using the 'help_text' argument
@ -2869,7 +2955,7 @@ actual field name.
{} {}
>>> p.is_valid() >>> p.is_valid()
True True
>>> p.clean_data >>> p.cleaned_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
Let's try submitting some bad data to make sure form.errors and field.errors Let's try submitting some bad data to make sure form.errors and field.errors
@ -2913,12 +2999,12 @@ of the same form.
>>> p1 = Person(data, prefix='person1') >>> p1 = Person(data, prefix='person1')
>>> p1.is_valid() >>> p1.is_valid()
True True
>>> p1.clean_data >>> p1.cleaned_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
>>> p2 = Person(data, prefix='person2') >>> p2 = Person(data, prefix='person2')
>>> p2.is_valid() >>> p2.is_valid()
True True
>>> p2.clean_data >>> p2.cleaned_data
{'first_name': u'Jim', 'last_name': u'Morrison', 'birthday': datetime.date(1943, 12, 8)} {'first_name': u'Jim', 'last_name': u'Morrison', 'birthday': datetime.date(1943, 12, 8)}
By default, forms append a hyphen between the prefix and the field name, but a By default, forms append a hyphen between the prefix and the field name, but a
@ -2944,7 +3030,7 @@ self.prefix.
>>> p = Person(data, prefix='foo') >>> p = Person(data, prefix='foo')
>>> p.is_valid() >>> p.is_valid()
True True
>>> p.clean_data >>> p.cleaned_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
# Forms with NullBooleanFields ################################################ # Forms with NullBooleanFields ################################################
@ -3006,16 +3092,16 @@ is different than its data. This is handled transparently, though.
... password1 = CharField(widget=PasswordInput) ... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput) ... password2 = CharField(widget=PasswordInput)
... def clean(self): ... def clean(self):
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']: ... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.') ... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data ... return self.cleaned_data
>>> def my_function(method, post_data): >>> def my_function(method, post_data):
... if method == 'POST': ... if method == 'POST':
... form = UserRegistration(post_data, auto_id=False) ... form = UserRegistration(post_data, auto_id=False)
... else: ... else:
... form = UserRegistration(auto_id=False) ... form = UserRegistration(auto_id=False)
... if form.is_valid(): ... if form.is_valid():
... return 'VALID: %r' % form.clean_data ... return 'VALID: %r' % form.cleaned_data
... t = Template('<form action="" method="post">\n<table>\n{{ form }}\n</table>\n<input type="submit" />\n</form>') ... t = Template('<form action="" method="post">\n<table>\n{{ form }}\n</table>\n<input type="submit" />\n</form>')
... return t.render(Context({'form': form})) ... return t.render(Context({'form': form}))
@ -3053,9 +3139,9 @@ VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'}
... password1 = CharField(widget=PasswordInput) ... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput) ... password2 = CharField(widget=PasswordInput)
... def clean(self): ... def clean(self):
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']: ... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.') ... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data ... return self.cleaned_data
You have full flexibility in displaying form fields in a template. Just pass a You have full flexibility in displaying form fields in a template. Just pass a
Form instance to the template, and use "dot" access to refer to individual Form instance to the template, and use "dot" access to refer to individual
@ -3405,7 +3491,7 @@ ValidationError: [u'This field is required.']
</select> </select>
<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr> <input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>
>>> f.clean_data >>> f.cleaned_data
{'field1': u'some text,JP,2007-04-25 06:24:00'} {'field1': u'some text,JP,2007-04-25 06:24:00'}
################################# #################################

View File

@ -101,6 +101,12 @@ class Anchor(models.Model):
data = models.CharField(maxlength=30) data = models.CharField(maxlength=30)
class UniqueAnchor(models.Model):
"""This is a model that can be used as
something for other models to point at"""
data = models.CharField(unique=True, maxlength=30)
class FKData(models.Model): class FKData(models.Model):
data = models.ForeignKey(Anchor, null=True) data = models.ForeignKey(Anchor, null=True)
@ -116,6 +122,10 @@ class FKSelfData(models.Model):
class M2MSelfData(models.Model): class M2MSelfData(models.Model):
data = models.ManyToManyField('self', null=True, symmetrical=False) data = models.ManyToManyField('self', null=True, symmetrical=False)
class FKDataToField(models.Model):
data = models.ForeignKey(UniqueAnchor, null=True, to_field='data')
# The following test classes are for validating the # The following test classes are for validating the
# deserialization of objects that use a user-defined # deserialization of objects that use a user-defined
# field as the primary key. # field as the primary key.

View File

@ -159,6 +159,7 @@ The end."""),
(data_obj, 300, Anchor, "Anchor 1"), (data_obj, 300, Anchor, "Anchor 1"),
(data_obj, 301, Anchor, "Anchor 2"), (data_obj, 301, Anchor, "Anchor 2"),
(data_obj, 302, UniqueAnchor, "UAnchor 1"),
(fk_obj, 400, FKData, 300), # Post reference (fk_obj, 400, FKData, 300), # Post reference
(fk_obj, 401, FKData, 500), # Pre reference (fk_obj, 401, FKData, 500), # Pre reference
@ -184,8 +185,13 @@ The end."""),
(m2m_obj, 445, M2MSelfData, []), (m2m_obj, 445, M2MSelfData, []),
(m2m_obj, 446, M2MSelfData, []), (m2m_obj, 446, M2MSelfData, []),
(fk_obj, 450, FKDataToField, "UAnchor 1"),
(fk_obj, 451, FKDataToField, "UAnchor 2"),
(fk_obj, 452, FKDataToField, None),
(data_obj, 500, Anchor, "Anchor 3"), (data_obj, 500, Anchor, "Anchor 3"),
(data_obj, 501, Anchor, "Anchor 4"), (data_obj, 501, Anchor, "Anchor 4"),
(data_obj, 502, UniqueAnchor, "UAnchor 2"),
(pk_obj, 601, BooleanPKData, True), (pk_obj, 601, BooleanPKData, True),
(pk_obj, 602, BooleanPKData, False), (pk_obj, 602, BooleanPKData, False),

View File

@ -61,6 +61,34 @@ class AssertTemplateUsedTests(TestCase):
except AssertionError, e: except AssertionError, e:
self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: ['form_view.html', 'base.html']") self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: ['form_view.html', 'base.html']")
class AssertRedirectsTests(TestCase):
def test_redirect_page(self):
"An assertion is raised if the original page couldn't be retrieved as expected"
# This page will redirect with code 301, not 302
response = self.client.get('/test_client/permanent_redirect_view/')
try:
self.assertRedirects(response, '/test_client/get_view/')
except AssertionError, e:
self.assertEquals(str(e), "Response didn't redirect as expected: Reponse code was 301 (expected 302)")
def test_incorrect_target(self):
"An assertion is raised if the response redirects to another target"
response = self.client.get('/test_client/permanent_redirect_view/')
try:
# Should redirect to get_view
self.assertRedirects(response, '/test_client/some_view/')
except AssertionError, e:
self.assertEquals(str(e), "Response didn't redirect as expected: Reponse code was 301 (expected 302)")
def test_target_page(self):
"An assertion is raised if the reponse redirect target cannot be retrieved as expected"
response = self.client.get('/test_client/double_redirect_view/')
try:
# The redirect target responds with a 301 code, not 200
self.assertRedirects(response, '/test_client/permanent_redirect_view/')
except AssertionError, e:
self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
class AssertFormErrorTests(TestCase): class AssertFormErrorTests(TestCase):
def test_unknown_form(self): def test_unknown_form(self):
"An assertion is raised if the form name is unknown" "An assertion is raised if the form name is unknown"