summaryrefslogtreecommitdiff
path: root/django/forms
diff options
context:
space:
mode:
Diffstat (limited to 'django/forms')
-rw-r--r--django/forms/fields.py22
-rw-r--r--django/forms/forms.py11
-rw-r--r--django/forms/formsets.py14
-rw-r--r--django/forms/models.py52
4 files changed, 70 insertions, 29 deletions
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 4ce57d34a3..ac68b9f1fc 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -198,14 +198,15 @@ class Field(object):
result.validators = self.validators[:]
return result
+
class CharField(Field):
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
self.max_length, self.min_length = max_length, min_length
super(CharField, self).__init__(*args, **kwargs)
if min_length is not None:
- self.validators.append(validators.MinLengthValidator(min_length))
+ self.validators.append(validators.MinLengthValidator(int(min_length)))
if max_length is not None:
- self.validators.append(validators.MaxLengthValidator(max_length))
+ self.validators.append(validators.MaxLengthValidator(int(max_length)))
def to_python(self, value):
"Returns a Unicode object."
@@ -220,6 +221,7 @@ class CharField(Field):
attrs.update({'maxlength': str(self.max_length)})
return attrs
+
class IntegerField(Field):
default_error_messages = {
'invalid': _('Enter a whole number.'),
@@ -444,6 +446,7 @@ class TimeField(BaseTemporalField):
def strptime(self, value, format):
return datetime.datetime.strptime(force_str(value), format).time()
+
class DateTimeField(BaseTemporalField):
widget = DateTimeInput
input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS')
@@ -482,6 +485,7 @@ class DateTimeField(BaseTemporalField):
def strptime(self, value, format):
return datetime.datetime.strptime(force_str(value), format)
+
class RegexField(CharField):
def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
"""
@@ -511,6 +515,7 @@ class RegexField(CharField):
regex = property(_get_regex, _set_regex)
+
class EmailField(CharField):
widget = EmailInput
default_validators = [validators.validate_email]
@@ -519,6 +524,7 @@ class EmailField(CharField):
value = self.to_python(value).strip()
return super(EmailField, self).clean(value)
+
class FileField(Field):
widget = ClearableFileInput
default_error_messages = {
@@ -626,6 +632,7 @@ class ImageField(FileField):
f.seek(0)
return f
+
class URLField(CharField):
widget = URLInput
default_error_messages = {
@@ -670,6 +677,10 @@ class URLField(CharField):
value = urlunsplit(url_fields)
return value
+ def clean(self, value):
+ value = self.to_python(value).strip()
+ return super(URLField, self).clean(value)
+
class BooleanField(Field):
widget = CheckboxInput
@@ -788,6 +799,7 @@ class ChoiceField(Field):
return True
return False
+
class TypedChoiceField(ChoiceField):
def __init__(self, *args, **kwargs):
self.coerce = kwargs.pop('coerce', lambda val: val)
@@ -899,6 +911,7 @@ class ComboField(Field):
value = field.clean(value)
return value
+
class MultiValueField(Field):
"""
A Field that aggregates the logic of multiple Fields.
@@ -1043,6 +1056,7 @@ class FilePathField(ChoiceField):
self.widget.choices = self.choices
+
class SplitDateTimeField(MultiValueField):
widget = SplitDateTimeWidget
hidden_widget = SplitHiddenDateTimeWidget
@@ -1105,3 +1119,7 @@ class GenericIPAddressField(CharField):
class SlugField(CharField):
default_validators = [validators.validate_slug]
+
+ def clean(self, value):
+ value = self.to_python(value).strip()
+ return super(SlugField, self).clean(value)
diff --git a/django/forms/forms.py b/django/forms/forms.py
index b231de421a..0c598ac775 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -134,7 +134,7 @@ class BaseForm(object):
Subclasses may wish to override.
"""
- return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
+ return '%s-%s' % (self.prefix, field_name) if self.prefix else field_name
def add_initial_prefix(self, field_name):
"""
@@ -342,6 +342,8 @@ class BaseForm(object):
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
if not field.show_hidden_initial:
initial_value = self.initial.get(name, field.initial)
+ if callable(initial_value):
+ initial_value = initial_value()
else:
initial_prefixed_name = self.add_initial_prefix(name)
hidden_widget = field.hidden_widget()
@@ -523,10 +525,11 @@ class BoundField(object):
widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id
if id_:
+ id_for_label = widget.id_for_label(id_)
+ if id_for_label:
+ attrs = dict(attrs or {}, **{'for': id_for_label})
attrs = flatatt(attrs) if attrs else ''
- contents = format_html('<label for="{0}"{1}>{2}</label>',
- widget.id_for_label(id_), attrs, contents
- )
+ contents = format_html('<label{0}>{1}</label>', attrs, contents)
else:
contents = conditional_escape(contents)
return mark_safe(contents)
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index d421770093..fd98c43405 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -250,9 +250,9 @@ class BaseFormSet(object):
form -- i.e., from formset.clean(). Returns an empty ErrorList if there
are none.
"""
- if self._non_form_errors is not None:
- return self._non_form_errors
- return self.error_class()
+ if self._non_form_errors is None:
+ self.full_clean()
+ return self._non_form_errors
@property
def errors(self):
@@ -291,16 +291,20 @@ class BaseFormSet(object):
def full_clean(self):
"""
- Cleans all of self.data and populates self._errors.
+ Cleans all of self.data and populates self._errors and
+ self._non_form_errors.
"""
self._errors = []
+ self._non_form_errors = self.error_class()
+
if not self.is_bound: # Stop further processing.
return
for i in range(0, self.total_form_count()):
form = self.forms[i]
self._errors.append(form.errors)
try:
- if (self.validate_max and self.total_form_count() > self.max_num) or \
+ if (self.validate_max and
+ self.total_form_count() - len(self.deleted_forms) > self.max_num) or \
self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
raise ValidationError(ungettext(
"Please submit %d or fewer forms.",
diff --git a/django/forms/models.py b/django/forms/models.py
index af5cda8faf..65434a6f6e 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -13,12 +13,12 @@ from django.forms.forms import BaseForm, get_declared_fields
from django.forms.formsets import BaseFormSet, formset_factory
from django.forms.util import ErrorList
from django.forms.widgets import (SelectMultiple, HiddenInput,
- MultipleHiddenInput, media_property)
+ MultipleHiddenInput, media_property, CheckboxSelectMultiple)
from django.utils.encoding import smart_text, force_text
from django.utils.datastructures import SortedDict
from django.utils import six
from django.utils.text import get_text_list, capfirst
-from django.utils.translation import ugettext_lazy as _, ugettext
+from django.utils.translation import ugettext_lazy as _, ugettext, string_concat
__all__ = (
@@ -85,6 +85,8 @@ def save_instance(form, instance, fields=None, fail_message='saved',
for f in opts.many_to_many:
if fields and f.name not in fields:
continue
+ if exclude and f.name in exclude:
+ continue
if f.name in cleaned_data:
f.save_form_data(instance, cleaned_data[f.name])
if commit:
@@ -136,7 +138,7 @@ def model_to_dict(instance, fields=None, exclude=None):
data[f.name] = f.value_from_object(instance)
return data
-def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
+def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, localized_fields=None):
"""
Returns a ``SortedDict`` containing form fields for the given model.
@@ -162,10 +164,12 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
continue
if exclude and f.name in exclude:
continue
+
+ kwargs = {}
if widgets and f.name in widgets:
- kwargs = {'widget': widgets[f.name]}
- else:
- kwargs = {}
+ kwargs['widget'] = widgets[f.name]
+ if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields):
+ kwargs['localize'] = True
if formfield_callback is None:
formfield = f.formfield(**kwargs)
@@ -192,6 +196,7 @@ class ModelFormOptions(object):
self.fields = getattr(options, 'fields', None)
self.exclude = getattr(options, 'exclude', None)
self.widgets = getattr(options, 'widgets', None)
+ self.localized_fields = getattr(options, 'localized_fields', None)
class ModelFormMetaclass(type):
@@ -215,7 +220,7 @@ class ModelFormMetaclass(type):
# We check if a string was passed to `fields` or `exclude`,
# which is likely to be a mistake where the user typed ('foo') instead
# of ('foo',)
- for opt in ['fields', 'exclude']:
+ for opt in ['fields', 'exclude', 'localized_fields']:
value = getattr(opts, opt)
if isinstance(value, six.string_types) and value != ALL_FIELDS:
msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
@@ -235,15 +240,16 @@ class ModelFormMetaclass(type):
warnings.warn("Creating a ModelForm without either the 'fields' attribute "
"or the 'exclude' attribute is deprecated - form %s "
"needs updating" % name,
- PendingDeprecationWarning)
+ PendingDeprecationWarning, stacklevel=2)
if opts.fields == ALL_FIELDS:
# sentinel for fields_for_model to indicate "get the list of
# fields from the model"
opts.fields = None
- fields = fields_for_model(opts.model, opts.fields,
- opts.exclude, opts.widgets, formfield_callback)
+ fields = fields_for_model(opts.model, opts.fields, opts.exclude,
+ opts.widgets, formfield_callback, opts.localized_fields)
+
# make sure opts.fields doesn't specify an invalid field
none_model_fields = [k for k, v in six.iteritems(fields) if not v]
missing_fields = set(none_model_fields) - \
@@ -401,7 +407,8 @@ class BaseModelForm(BaseForm):
else:
fail_message = 'changed'
return save_instance(self, self.instance, self._meta.fields,
- fail_message, commit, construct=False)
+ fail_message, commit, self._meta.exclude,
+ construct=False)
save.alters_data = True
@@ -409,7 +416,7 @@ class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)):
pass
def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
- formfield_callback=None, widgets=None):
+ formfield_callback=None, widgets=None, localized_fields=None):
"""
Returns a ModelForm containing form fields for the given model.
@@ -423,6 +430,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
``widgets`` is a dictionary of model field names mapped to a widget.
+ ``localized_fields`` is a list of names of fields which should be localized.
+
``formfield_callback`` is a callable that takes a model field and returns
a form field.
"""
@@ -438,6 +447,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
attrs['exclude'] = exclude
if widgets is not None:
attrs['widgets'] = widgets
+ if localized_fields is not None:
+ attrs['localized_fields'] = localized_fields
# If parent form class already has an inner Meta, the Meta we're
# creating needs to inherit from the parent's inner meta.
@@ -726,8 +737,8 @@ class BaseModelFormSet(BaseFormSet):
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
formset=BaseModelFormSet, extra=1, can_delete=False,
- can_order=False, max_num=None, fields=None,
- exclude=None, widgets=None, validate_max=False):
+ can_order=False, max_num=None, fields=None, exclude=None,
+ widgets=None, validate_max=False, localized_fields=None):
"""
Returns a FormSet class for the given Django model class.
"""
@@ -748,7 +759,7 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
formfield_callback=formfield_callback,
- widgets=widgets)
+ widgets=widgets, localized_fields=localized_fields)
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
can_order=can_order, can_delete=can_delete,
validate_max=validate_max)
@@ -885,9 +896,9 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
def inlineformset_factory(parent_model, model, form=ModelForm,
formset=BaseInlineFormSet, fk_name=None,
- fields=None, exclude=None,
- extra=3, can_order=False, can_delete=True, max_num=None,
- formfield_callback=None, widgets=None, validate_max=False):
+ fields=None, exclude=None, extra=3, can_order=False,
+ can_delete=True, max_num=None, formfield_callback=None,
+ widgets=None, validate_max=False, localized_fields=None):
"""
Returns an ``InlineFormSet`` for the given kwargs.
@@ -910,6 +921,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
'max_num': max_num,
'widgets': widgets,
'validate_max': validate_max,
+ 'localized_fields': localized_fields,
}
FormSet = modelformset_factory(model, **kwargs)
FormSet.fk = fk
@@ -1095,6 +1107,10 @@ class ModelMultipleChoiceField(ModelChoiceField):
super(ModelMultipleChoiceField, self).__init__(queryset, None,
cache_choices, required, widget, label, initial, help_text,
*args, **kwargs)
+ # Remove this in Django 1.8
+ if isinstance(self.widget, SelectMultiple) and not isinstance(self.widget, CheckboxSelectMultiple):
+ msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
+ self.help_text = string_concat(self.help_text, ' ', msg)
def clean(self, value):
if self.required and not value: