Skip to content

Commit

Permalink
Fixed #3102 -- newforms: Fields can now designate their human-friendl…
Browse files Browse the repository at this point in the history
…y labels. BoundField.verbose_name is now BoundField.label

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4188 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
adrianholovaty committed Dec 8, 2006
1 parent f10a910 commit d93021e
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 35 deletions.
40 changes: 20 additions & 20 deletions django/newforms/fields.py
Expand Up @@ -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()
Expand Down Expand Up @@ -69,9 +69,9 @@ def widget_attrs(self, widget):
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."
Expand Down Expand Up @@ -111,8 +111,8 @@ def clean(self, value):
) )


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):
Expand Down Expand Up @@ -147,8 +147,8 @@ def clean(self, value):
) )


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):
Expand All @@ -171,13 +171,13 @@ def clean(self, value):
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
Expand All @@ -203,8 +203,8 @@ def clean(self, value):
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://
Expand All @@ -220,9 +220,9 @@ def __init__(self, required=True, widget=None):
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


Expand Down Expand Up @@ -256,10 +256,10 @@ def clean(self, value):
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):
Expand All @@ -277,8 +277,8 @@ def clean(self, value):
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):
""" """
Expand All @@ -302,8 +302,8 @@ def clean(self, value):
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.
Expand Down
11 changes: 4 additions & 7 deletions django/newforms/forms.py
Expand Up @@ -86,7 +86,7 @@ def _html_output(self, normal_row, error_row, row_ender, errors_on_separate_row)
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.
Expand Down Expand Up @@ -164,6 +164,7 @@ def __init__(self, form, field, name):
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."
Expand Down Expand Up @@ -213,17 +214,13 @@ def _data(self):
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_:
Expand Down
34 changes: 26 additions & 8 deletions tests/regressiontests/forms/tests.py
Expand Up @@ -636,6 +636,9 @@
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.
Expand Down Expand Up @@ -1335,7 +1338,7 @@
<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
Expand Down Expand Up @@ -1908,6 +1911,19 @@
<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
Expand Down Expand Up @@ -1994,12 +2010,14 @@
<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()}))
Expand All @@ -2010,8 +2028,8 @@
<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="">
Expand Down

0 comments on commit d93021e

Please sign in to comment.