mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
boulder-oracle-sprint: Merged to [5234]
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5235 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
bcc26d8eea
commit
c53378eb4b
2
AUTHORS
2
AUTHORS
@ -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/>
|
||||||
|
@ -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')),
|
||||||
|
BIN
django/conf/locale/bg/LC_MESSAGES/django.mo
Normal file
BIN
django/conf/locale/bg/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2670
django/conf/locale/bg/LC_MESSAGES/django.po
Normal file
2670
django/conf/locale/bg/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
django/conf/locale/bg/LC_MESSAGES/djangojs.mo
Normal file
BIN
django/conf/locale/bg/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
106
django/conf/locale/bg/LC_MESSAGES/djangojs.po
Normal file
106
django/conf/locale/bg/LC_MESSAGES/djangojs.po
Normal 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 "Утре"
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -1463,21 +1463,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, ..."
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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):
|
||||||
|
@ -242,7 +242,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'),
|
||||||
@ -251,7 +251,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'),
|
||||||
|
@ -199,7 +199,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'),
|
||||||
@ -208,7 +208,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'),
|
||||||
|
@ -184,8 +184,8 @@ class BaseForm(StrAndUnicode):
|
|||||||
try:
|
try:
|
||||||
value = field.clean(value)
|
value = field.clean(value)
|
||||||
self.clean_data[name] = value
|
self.clean_data[name] = value
|
||||||
if hasattr(self, 'clean_%s' % name):
|
if hasattr(self, 'do_clean_%s' % name):
|
||||||
value = getattr(self, 'clean_%s' % name)()
|
value = getattr(self, 'do_clean_%s' % name)()
|
||||||
self.clean_data[name] = value
|
self.clean_data[name] = value
|
||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
errors[name] = e.messages
|
errors[name] = e.messages
|
||||||
@ -255,6 +255,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)
|
||||||
|
@ -12,17 +12,7 @@ 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.
|
|
||||||
|
|
||||||
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``.
|
Saves bound Form ``form``'s clean_data into model instance ``instance``.
|
||||||
|
|
||||||
@ -33,15 +23,19 @@ 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
|
clean_data = form.clean_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 clean_data:
|
||||||
continue
|
continue
|
||||||
|
if fields and f.name not in fields:
|
||||||
|
continue
|
||||||
setattr(instance, f.name, clean_data[f.name])
|
setattr(instance, f.name, clean_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 fields and f.name not in fields:
|
||||||
|
continue
|
||||||
if f.name in clean_data:
|
if f.name in clean_data:
|
||||||
setattr(instance, f.attname, clean_data[f.name])
|
setattr(instance, f.attname, clean_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
|
||||||
@ -50,13 +44,19 @@ def save_instance(form, instance, commit=True):
|
|||||||
# 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."
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -274,6 +280,27 @@ but ``clean_data`` contains only the form's fields::
|
|||||||
>>> f.clean_data # Doesn't contain extra_field_1, etc.
|
>>> f.clean_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'}
|
||||||
|
|
||||||
|
``clean_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 ``clean_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.clean_data
|
||||||
|
{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'}
|
||||||
|
|
||||||
|
In this above example, the ``clean_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
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -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
|
|
||||||
=======
|
|
||||||
|
@ -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.
|
||||||
|
@ -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's a test</option>
|
<option value="2">It'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()
|
||||||
|
@ -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.clean_data
|
||||||
|
{'data': u'xyzzy'}
|
||||||
"""
|
"""
|
||||||
|
@ -1916,6 +1916,34 @@ True
|
|||||||
>>> p.clean_data
|
>>> p.clean_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)}
|
||||||
|
|
||||||
|
clean_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 clean_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.clean_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.clean_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
|
||||||
@ -2275,7 +2303,7 @@ returns a list of input.
|
|||||||
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 do_clean_special_name(self):
|
||||||
... raise ValidationError("Something's wrong with '%s'" % self.clean_data['special_name'])
|
... raise ValidationError("Something's wrong with '%s'" % self.clean_data['special_name'])
|
||||||
|
|
||||||
>>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False)
|
>>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False)
|
||||||
@ -2298,7 +2326,7 @@ including the current field (e.g., the field XXX if you're in clean_XXX()).
|
|||||||
... username = CharField(max_length=10)
|
... username = CharField(max_length=10)
|
||||||
... password1 = CharField(widget=PasswordInput)
|
... password1 = CharField(widget=PasswordInput)
|
||||||
... password2 = CharField(widget=PasswordInput)
|
... password2 = CharField(widget=PasswordInput)
|
||||||
... def clean_password2(self):
|
... def do_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.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
|
||||||
... raise ValidationError(u'Please make sure your passwords match.')
|
... raise ValidationError(u'Please make sure your passwords match.')
|
||||||
... return self.clean_data['password2']
|
... return self.clean_data['password2']
|
||||||
@ -2752,6 +2780,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
|
||||||
|
@ -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.
|
||||||
|
@ -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),
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user