Fixed #3102 -- newforms: Fields can now designate their human-friendly labels. BoundField.verbose_name is now BoundField.label

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4188 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-12-08 20:06:12 +00:00
parent f10a910577
commit d93021eb10
3 changed files with 50 additions and 35 deletions

View File

@ -32,8 +32,8 @@ 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): def __init__(self, required=True, widget=None, label=None):
self.required = required self.required, self.label = required, label
widget = widget or self.widget widget = widget or self.widget
if isinstance(widget, type): if isinstance(widget, type):
widget = widget() widget = widget()
@ -69,9 +69,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): def __init__(self, max_length=None, min_length=None, required=True, widget=None, label=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) Field.__init__(self, required, widget, label)
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."
@ -111,8 +111,8 @@ DEFAULT_DATE_INPUT_FORMATS = (
) )
class DateField(Field): class DateField(Field):
def __init__(self, input_formats=None, required=True, widget=None): def __init__(self, input_formats=None, required=True, widget=None, label=None):
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label)
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):
@ -147,8 +147,8 @@ DEFAULT_DATETIME_INPUT_FORMATS = (
) )
class DateTimeField(Field): class DateTimeField(Field):
def __init__(self, input_formats=None, required=True, widget=None): def __init__(self, input_formats=None, required=True, widget=None, label=None):
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label)
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):
@ -171,13 +171,13 @@ class DateTimeField(Field):
raise ValidationError(gettext(u'Enter a valid date/time.')) raise ValidationError(gettext(u'Enter a valid date/time.'))
class RegexField(Field): class RegexField(Field):
def __init__(self, regex, error_message=None, required=True, widget=None): def __init__(self, regex, error_message=None, required=True, widget=None, label=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) Field.__init__(self, required, widget, label)
if isinstance(regex, basestring): if isinstance(regex, basestring):
regex = re.compile(regex) regex = re.compile(regex)
self.regex = regex self.regex = regex
@ -203,8 +203,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, required=True, widget=None): def __init__(self, required=True, widget=None, label=None):
RegexField.__init__(self, email_re, gettext(u'Enter a valid e-mail address.'), required, widget) RegexField.__init__(self, email_re, gettext(u'Enter a valid e-mail address.'), required, widget, label)
url_re = re.compile( url_re = re.compile(
r'^https?://' # http:// or https:// r'^https?://' # http:// or https://
@ -220,9 +220,9 @@ except ImportError:
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
class URLField(RegexField): class URLField(RegexField):
def __init__(self, required=True, verify_exists=False, widget=None, def __init__(self, required=True, verify_exists=False, widget=None, label=None,
validator_user_agent=URL_VALIDATOR_USER_AGENT): validator_user_agent=URL_VALIDATOR_USER_AGENT):
RegexField.__init__(self, url_re, gettext(u'Enter a valid URL.'), required, widget) RegexField.__init__(self, url_re, gettext(u'Enter a valid URL.'), required, widget, label)
self.verify_exists = verify_exists self.verify_exists = verify_exists
self.user_agent = validator_user_agent self.user_agent = validator_user_agent
@ -256,10 +256,10 @@ class BooleanField(Field):
return bool(value) return bool(value)
class ChoiceField(Field): class ChoiceField(Field):
def __init__(self, choices=(), required=True, widget=Select): def __init__(self, choices=(), required=True, widget=Select, label=None):
if isinstance(widget, type): if isinstance(widget, type):
widget = widget(choices=choices) widget = widget(choices=choices)
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label)
self.choices = choices self.choices = choices
def clean(self, value): def clean(self, value):
@ -277,8 +277,8 @@ class ChoiceField(Field):
return value return value
class MultipleChoiceField(ChoiceField): class MultipleChoiceField(ChoiceField):
def __init__(self, choices=(), required=True, widget=SelectMultiple): def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None):
ChoiceField.__init__(self, choices, required, widget) ChoiceField.__init__(self, choices, required, widget, label)
def clean(self, value): def clean(self, value):
""" """
@ -302,8 +302,8 @@ class MultipleChoiceField(ChoiceField):
return new_value return new_value
class ComboField(Field): class ComboField(Field):
def __init__(self, fields=(), required=True, widget=None): def __init__(self, fields=(), required=True, widget=None, label=None):
Field.__init__(self, required, widget) Field.__init__(self, required, widget, label)
# 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

@ -86,7 +86,7 @@ class Form(StrAndUnicode):
else: else:
if errors_on_separate_row and bf_errors: if errors_on_separate_row and bf_errors:
output.append(error_row % bf_errors) output.append(error_row % bf_errors)
output.append(normal_row % {'errors': bf_errors, 'label': bf.label_tag(escape(bf.verbose_name+':')), 'field': bf}) output.append(normal_row % {'errors': bf_errors, 'label': bf.label_tag(escape(bf.label+':')), 'field': bf})
if top_errors: if top_errors:
output.insert(0, error_row % top_errors) output.insert(0, error_row % top_errors)
if hidden_fields: # Insert any hidden fields in the last row. if hidden_fields: # Insert any hidden fields in the last row.
@ -164,6 +164,7 @@ class BoundField(StrAndUnicode):
self.form = form self.form = form
self.field = field self.field = field
self.name = name self.name = name
self.label = self.field.label or pretty_name(name)
def __unicode__(self): def __unicode__(self):
"Renders this field as an HTML widget." "Renders this field as an HTML widget."
@ -213,17 +214,13 @@ class BoundField(StrAndUnicode):
return self.form.data.get(self.name, None) return self.form.data.get(self.name, None)
data = property(_data) data = property(_data)
def _verbose_name(self):
return pretty_name(self.name)
verbose_name = property(_verbose_name)
def label_tag(self, contents=None): def label_tag(self, contents=None):
""" """
Wraps the given contents in a <label>, if the field has an ID attribute. Wraps the given contents in a <label>, if the field has an ID attribute.
Does not HTML-escape the contents. If contents aren't given, uses the Does not HTML-escape the contents. If contents aren't given, uses the
field's HTML-escaped verbose_name. field's HTML-escaped label.
""" """
contents = contents or escape(self.verbose_name) contents = contents or escape(self.label)
widget = self.field.widget widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id id_ = widget.attrs.get('id') or self.auto_id
if id_: if id_:

View File

@ -636,6 +636,9 @@ Each Field's __init__() takes at least these parameters:
used for this Field when displaying it. Each Field has a default 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, Widget that it'll use if you don't specify this. In most cases,
the default widget is TextInput. 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.
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.
@ -1335,7 +1338,7 @@ u''
<input type="text" name="last_name" value="Lennon" /> <input type="text" name="last_name" value="Lennon" />
<input type="text" name="birthday" value="1940-10-9" /> <input type="text" name="birthday" value="1940-10-9" />
>>> for boundfield in p: >>> for boundfield in p:
... print boundfield.verbose_name, boundfield.data ... print boundfield.label, boundfield.data
First name John First name John
Last name Lennon Last name Lennon
Birthday 1940-10-9 Birthday 1940-10-9
@ -1908,6 +1911,19 @@ 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>
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
underscores converted to spaces, and the initial letter capitalized.
>>> class UserRegistration(Form):
... username = CharField(max_length=10, label='Your username')
... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput, label='Password (again)')
>>> p = UserRegistration()
>>> print p.as_ul()
<li>Your username: <input type="text" name="username" maxlength="10" /></li>
<li>Password1: <input type="password" name="password1" /></li>
<li>Password (again): <input type="password" name="password2" /></li>
# Basic form processing in a view ############################################# # Basic form processing in a view #############################################
>>> from django.template import Template, Context >>> from django.template import Template, Context
@ -1994,12 +2010,14 @@ particular field.
<input type="submit" /> <input type="submit" />
</form> </form>
Use form.[field].verbose_name to output a field's "verbose name" -- its field Use form.[field].label to output a field's label. You can specify the label for
name with underscores converted to spaces, and the initial letter capitalized. a field by using the 'label' argument to a Field class. If you don't specify
'label', Django will use the field name with underscores converted to spaces,
and the initial letter capitalized.
>>> t = Template('''<form action=""> >>> t = Template('''<form action="">
... <p><label>{{ form.username.verbose_name }}: {{ form.username }}</label></p> ... <p><label>{{ form.username.label }}: {{ form.username }}</label></p>
... <p><label>{{ form.password1.verbose_name }}: {{ form.password1 }}</label></p> ... <p><label>{{ form.password1.label }}: {{ form.password1 }}</label></p>
... <p><label>{{ form.password2.verbose_name }}: {{ form.password2 }}</label></p> ... <p><label>{{ form.password2.label }}: {{ form.password2 }}</label></p>
... <input type="submit" /> ... <input type="submit" />
... </form>''') ... </form>''')
>>> print t.render(Context({'form': UserRegistration()})) >>> print t.render(Context({'form': UserRegistration()}))
@ -2010,8 +2028,8 @@ name with underscores converted to spaces, and the initial letter capitalized.
<input type="submit" /> <input type="submit" />
</form> </form>
User form.[field].label_tag to output a field's verbose_name with a <label> User form.[field].label_tag to output a field's label with a <label> tag
tag wrapped around it, but *only* if the given field has an "id" attribute. wrapped around it, but *only* if the given field has an "id" attribute.
Recall from above that passing the "auto_id" argument to a Form gives each Recall from above that passing the "auto_id" argument to a Form gives each
field an "id" attribute. field an "id" attribute.
>>> t = Template('''<form action=""> >>> t = Template('''<form action="">