newforms: Added 'initial' parameter to Field. This allows you to specify initial data that will be displayed with the form if no data is given. Also added unit tests.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4249 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-12-28 00:01:52 +00:00
parent 016d75cc7f
commit 2cb0fe71a2
3 changed files with 87 additions and 26 deletions

View File

@ -33,10 +33,21 @@ class Field(object):
# Tracks each time a Field instance is created. Used to retain order. # Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0 creation_counter = 0
def __init__(self, required=True, widget=None, label=None): def __init__(self, required=True, widget=None, label=None, initial=None):
# required -- Boolean that specifies whether the field is required.
# True by default.
# widget -- A Widget class, or instance of a Widget class, that should be
# used for this Field when displaying it. Each Field has a default
# Widget that it'll use if you don't specify this. In most cases,
# the default widget is TextInput.
# label -- A verbose name for this field, for use in displaying this field in
# a form. By default, Django will use a "pretty" version of the form
# field name, if the Field is part of a Form.
# initial -- A value to use in this Field's initial display. This value is
# *not* used as a fallback if data isn't given.
if label is not None: if label is not None:
label = smart_unicode(label) label = smart_unicode(label)
self.required, self.label = required, label self.required, self.label, self.initial = required, label, initial
widget = widget or self.widget widget = widget or self.widget
if isinstance(widget, type): if isinstance(widget, type):
widget = widget() widget = widget()
@ -72,9 +83,9 @@ class Field(object):
return {} return {}
class CharField(Field): class CharField(Field):
def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None): def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None, initial=None):
self.max_length, self.min_length = max_length, min_length self.max_length, self.min_length = max_length, min_length
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
def clean(self, value): def clean(self, value):
"Validates max_length and min_length. Returns a Unicode object." "Validates max_length and min_length. Returns a Unicode object."
@ -95,9 +106,9 @@ class CharField(Field):
return {'maxlength': str(self.max_length)} return {'maxlength': str(self.max_length)}
class IntegerField(Field): class IntegerField(Field):
def __init__(self, max_value=None, min_value=None, required=True, widget=None, label=None): def __init__(self, max_value=None, min_value=None, required=True, widget=None, label=None, initial=None):
self.max_value, self.min_value = max_value, min_value self.max_value, self.min_value = max_value, min_value
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
def clean(self, value): def clean(self, value):
""" """
@ -126,8 +137,8 @@ DEFAULT_DATE_INPUT_FORMATS = (
) )
class DateField(Field): class DateField(Field):
def __init__(self, input_formats=None, required=True, widget=None, label=None): def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
def clean(self, value): def clean(self, value):
@ -155,8 +166,8 @@ DEFAULT_TIME_INPUT_FORMATS = (
) )
class TimeField(Field): class TimeField(Field):
def __init__(self, input_formats=None, required=True, widget=None, label=None): def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
def clean(self, value): def clean(self, value):
@ -189,8 +200,8 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
) )
class DateTimeField(Field): class DateTimeField(Field):
def __init__(self, input_formats=None, required=True, widget=None, label=None): def __init__(self, input_formats=None, required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
def clean(self, value): def clean(self, value):
@ -214,13 +225,13 @@ class DateTimeField(Field):
class RegexField(Field): class RegexField(Field):
def __init__(self, regex, max_length=None, min_length=None, error_message=None, def __init__(self, regex, max_length=None, min_length=None, error_message=None,
required=True, widget=None, label=None): required=True, widget=None, label=None, initial=None):
""" """
regex can be either a string or a compiled regular expression object. regex can be either a string or a compiled regular expression object.
error_message is an optional error message to use, if error_message is an optional error message to use, if
'Enter a valid value' is too generic for you. 'Enter a valid value' is too generic for you.
""" """
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
if isinstance(regex, basestring): if isinstance(regex, basestring):
regex = re.compile(regex) regex = re.compile(regex)
self.regex = regex self.regex = regex
@ -251,8 +262,8 @@ email_re = re.compile(
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
class EmailField(RegexField): class EmailField(RegexField):
def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None): def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=None, initial=None):
RegexField.__init__(self, email_re, max_length, min_length, gettext(u'Enter a valid e-mail address.'), required, widget, label) RegexField.__init__(self, email_re, max_length, min_length, gettext(u'Enter a valid e-mail address.'), required, widget, label, initial)
url_re = re.compile( url_re = re.compile(
r'^https?://' # http:// or https:// r'^https?://' # http:// or https://
@ -269,8 +280,8 @@ except ImportError:
class URLField(RegexField): class URLField(RegexField):
def __init__(self, max_length=None, min_length=None, required=True, verify_exists=False, widget=None, label=None, def __init__(self, max_length=None, min_length=None, required=True, verify_exists=False, widget=None, label=None,
validator_user_agent=URL_VALIDATOR_USER_AGENT): initial=None, validator_user_agent=URL_VALIDATOR_USER_AGENT):
RegexField.__init__(self, url_re, max_length, min_length, gettext(u'Enter a valid URL.'), required, widget, label) RegexField.__init__(self, url_re, max_length, min_length, gettext(u'Enter a valid URL.'), required, widget, label, initial)
self.verify_exists = verify_exists self.verify_exists = verify_exists
self.user_agent = validator_user_agent self.user_agent = validator_user_agent
@ -304,10 +315,10 @@ class BooleanField(Field):
return bool(value) return bool(value)
class ChoiceField(Field): class ChoiceField(Field):
def __init__(self, choices=(), required=True, widget=Select, label=None): def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None):
if isinstance(widget, type): if isinstance(widget, type):
widget = widget(choices=choices) widget = widget(choices=choices)
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
self.choices = choices self.choices = choices
def clean(self, value): def clean(self, value):
@ -325,8 +336,8 @@ class ChoiceField(Field):
return value return value
class MultipleChoiceField(ChoiceField): class MultipleChoiceField(ChoiceField):
def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None): def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None):
ChoiceField.__init__(self, choices, required, widget, label) ChoiceField.__init__(self, choices, required, widget, label, initial)
def clean(self, value): def clean(self, value):
""" """
@ -350,8 +361,8 @@ class MultipleChoiceField(ChoiceField):
return new_value return new_value
class ComboField(Field): class ComboField(Field):
def __init__(self, fields=(), required=True, widget=None, label=None): def __init__(self, fields=(), required=True, widget=None, label=None, initial=None):
Field.__init__(self, required, widget, label) Field.__init__(self, required, widget, label, initial)
# Set 'required' to False on the individual fields, because the # Set 'required' to False on the individual fields, because the
# required validation will be handled by ComboField, not by those # required validation will be handled by ComboField, not by those
# individual fields. # individual fields.

View File

@ -218,7 +218,11 @@ class BoundField(StrAndUnicode):
auto_id = self.auto_id auto_id = self.auto_id
if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'): if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
attrs['id'] = auto_id attrs['id'] = auto_id
return widget.render(self.html_name, self.data, attrs=attrs) if self.form.ignore_errors:
data = self.field.initial
else:
data = self.data
return widget.render(self.html_name, data, attrs=attrs)
def as_text(self, attrs=None): def as_text(self, attrs=None):
""" """
@ -237,7 +241,9 @@ class BoundField(StrAndUnicode):
return self.as_widget(HiddenInput(), attrs) return self.as_widget(HiddenInput(), attrs)
def _data(self): def _data(self):
"Returns the data for this BoundField, or None if it wasn't given." """
Returns the data for this BoundField, or None if it wasn't given.
"""
return self.field.widget.value_from_datadict(self.form.data, self.html_name) return self.field.widget.value_from_datadict(self.form.data, self.html_name)
data = property(_data) data = property(_data)

View File

@ -658,6 +658,8 @@ Each Field's __init__() takes at least these parameters:
label -- A verbose name for this field, for use in displaying this field in label -- A verbose name for this field, for use in displaying this field in
a form. By default, Django will use a "pretty" version of the form a form. By default, Django will use a "pretty" version of the form
field name, if the Field is part of a Form. field name, if the Field is part of a Form.
initial -- A value to use in this Field's initial display. This value is
*not* used as a fallback if data isn't given.
Other than that, the Field subclasses have class-specific options for Other than that, the Field subclasses have class-specific options for
__init__(). For example, CharField has a max_length option. __init__(). For example, CharField has a max_length option.
@ -2108,6 +2110,8 @@ in "attrs".
<li>Username: <input type="text" name="username" maxlength="10" /></li> <li>Username: <input type="text" name="username" maxlength="10" /></li>
<li>Password: <input type="password" name="password" maxlength="10" /></li> <li>Password: <input type="password" name="password" maxlength="10" /></li>
# Specifying labels ###########################################################
You can specify the label for a field by using the 'label' argument to a Field You can specify the label for a field by using the 'label' argument to a Field
class. If you don't specify 'label', Django will use the field name with class. If you don't specify 'label', Django will use the field name with
underscores converted to spaces, and the initial letter capitalized. underscores converted to spaces, and the initial letter capitalized.
@ -2156,6 +2160,46 @@ is default behavior.
<li><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></li> <li><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></li>
<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li> <li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>
# Initial data ################################################################
You can specify initial data for a field by using the 'initial' argument to a
Field class. This initial data is displayed when a Form is rendered with *no*
data. It is not displayed when a Form is rendered with any data (including an
empty dictionary). Also, the initial value is *not* used if data for a
particular required field isn't provided.
>>> class UserRegistration(Form):
... username = CharField(max_length=10, initial='django')
... password = CharField(widget=PasswordInput)
Here, we're not submitting any data, so the initial value will be displayed.
>>> 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>
Here, we're submitting data, so the initial value will *not* be displayed.
>>> p = UserRegistration({}, 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''}, 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'}, 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>
An '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'})
>>> p.errors
{'username': [u'This field is required.']}
>>> p.is_valid()
False
# Forms with prefixes ######################################################### # Forms with prefixes #########################################################
Sometimes it's necessary to have multiple forms display on the same HTML page, Sometimes it's necessary to have multiple forms display on the same HTML page,