Fixed #12512. Changed ModelForm to stop performing model validation on fields that are not part of the form. Thanks, Honza Kral and Ivan Sagalaev.
This reverts some admin and test changes from [12098] and also fixes #12507, #12520, #12552 and #12553. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12206 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
26279c5721
commit
2f9853b2dc
|
@ -579,12 +579,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"""
|
"""
|
||||||
messages.info(request, message)
|
messages.info(request, message)
|
||||||
|
|
||||||
def save_form(self, request, form, change, commit=False):
|
def save_form(self, request, form, change):
|
||||||
"""
|
"""
|
||||||
Given a ModelForm return an unsaved instance. ``change`` is True if
|
Given a ModelForm return an unsaved instance. ``change`` is True if
|
||||||
the object is being changed, and False if it's being added.
|
the object is being changed, and False if it's being added.
|
||||||
"""
|
"""
|
||||||
return form.save(commit=commit)
|
return form.save(commit=False)
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
"""
|
"""
|
||||||
|
@ -758,11 +758,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = ModelForm(request.POST, request.FILES)
|
form = ModelForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
# Save the object, even if inline formsets haven't been
|
new_object = self.save_form(request, form, change=False)
|
||||||
# validated yet. We need to pass the valid model to the
|
|
||||||
# formsets for validation. If the formsets do not validate, we
|
|
||||||
# will delete the object.
|
|
||||||
new_object = self.save_form(request, form, change=False, commit=True)
|
|
||||||
form_validated = True
|
form_validated = True
|
||||||
else:
|
else:
|
||||||
form_validated = False
|
form_validated = False
|
||||||
|
@ -779,15 +775,13 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
prefix=prefix, queryset=inline.queryset(request))
|
prefix=prefix, queryset=inline.queryset(request))
|
||||||
formsets.append(formset)
|
formsets.append(formset)
|
||||||
if all_valid(formsets) and form_validated:
|
if all_valid(formsets) and form_validated:
|
||||||
|
self.save_model(request, new_object, form, change=False)
|
||||||
|
form.save_m2m()
|
||||||
for formset in formsets:
|
for formset in formsets:
|
||||||
self.save_formset(request, form, formset, change=False)
|
self.save_formset(request, form, formset, change=False)
|
||||||
|
|
||||||
self.log_addition(request, new_object)
|
self.log_addition(request, new_object)
|
||||||
return self.response_add(request, new_object)
|
return self.response_add(request, new_object)
|
||||||
elif form_validated:
|
|
||||||
# The form was valid, but formsets were not, so delete the
|
|
||||||
# object we saved above.
|
|
||||||
new_object.delete()
|
|
||||||
else:
|
else:
|
||||||
# Prepare the dict of initial data from the request.
|
# Prepare the dict of initial data from the request.
|
||||||
# We have to special-case M2Ms as a list of comma-separated PKs.
|
# We have to special-case M2Ms as a list of comma-separated PKs.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.contrib.auth.models import User, UNUSABLE_PASSWORD
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
@ -21,12 +21,6 @@ class UserCreationForm(forms.ModelForm):
|
||||||
model = User
|
model = User
|
||||||
fields = ("username",)
|
fields = ("username",)
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
# Fill the password field so model validation won't complain about it
|
|
||||||
# being blank. We'll set it with the real value below.
|
|
||||||
self.instance.password = UNUSABLE_PASSWORD
|
|
||||||
super(UserCreationForm, self).clean()
|
|
||||||
|
|
||||||
def clean_username(self):
|
def clean_username(self):
|
||||||
username = self.cleaned_data["username"]
|
username = self.cleaned_data["username"]
|
||||||
try:
|
try:
|
||||||
|
@ -40,9 +34,15 @@ class UserCreationForm(forms.ModelForm):
|
||||||
password2 = self.cleaned_data["password2"]
|
password2 = self.cleaned_data["password2"]
|
||||||
if password1 != password2:
|
if password1 != password2:
|
||||||
raise forms.ValidationError(_("The two password fields didn't match."))
|
raise forms.ValidationError(_("The two password fields didn't match."))
|
||||||
self.instance.set_password(password1)
|
|
||||||
return password2
|
return password2
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
user = super(UserCreationForm, self).save(commit=False)
|
||||||
|
user.set_password(self.cleaned_data["password1"])
|
||||||
|
if commit:
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
class UserChangeForm(forms.ModelForm):
|
class UserChangeForm(forms.ModelForm):
|
||||||
username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$',
|
username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$',
|
||||||
help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."),
|
help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."),
|
||||||
|
|
|
@ -33,7 +33,7 @@ class FieldError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
NON_FIELD_ERRORS = '__all__'
|
NON_FIELD_ERRORS = '__all__'
|
||||||
class BaseValidationError(Exception):
|
class ValidationError(Exception):
|
||||||
"""An error while validating data."""
|
"""An error while validating data."""
|
||||||
def __init__(self, message, code=None, params=None):
|
def __init__(self, message, code=None, params=None):
|
||||||
import operator
|
import operator
|
||||||
|
@ -64,10 +64,14 @@ class BaseValidationError(Exception):
|
||||||
return repr(self.message_dict)
|
return repr(self.message_dict)
|
||||||
return repr(self.messages)
|
return repr(self.messages)
|
||||||
|
|
||||||
class ValidationError(BaseValidationError):
|
def update_error_dict(self, error_dict):
|
||||||
pass
|
if hasattr(self, 'message_dict'):
|
||||||
|
if error_dict:
|
||||||
class UnresolvableValidationError(BaseValidationError):
|
for k, v in self.message_dict.items():
|
||||||
"""Validation error that cannot be resolved by the user."""
|
error_dict.setdefault(k, []).extend(v)
|
||||||
pass
|
else:
|
||||||
|
error_dict = self.message_dict
|
||||||
|
else:
|
||||||
|
error_dict[NON_FIELD_ERRORS] = self.messages
|
||||||
|
return error_dict
|
||||||
|
|
||||||
|
|
|
@ -640,17 +640,21 @@ class Model(object):
|
||||||
def prepare_database_save(self, unused):
|
def prepare_database_save(self, unused):
|
||||||
return self.pk
|
return self.pk
|
||||||
|
|
||||||
def validate(self):
|
def clean(self):
|
||||||
"""
|
"""
|
||||||
Hook for doing any extra model-wide validation after clean() has been
|
Hook for doing any extra model-wide validation after clean() has been
|
||||||
called on every field. Any ValidationError raised by this method will
|
called on every field by self.clean_fields. Any ValidationError raised
|
||||||
not be associated with a particular field; it will have a special-case
|
by this method will not be associated with a particular field; it will
|
||||||
association with the field defined by NON_FIELD_ERRORS.
|
have a special-case association with the field defined by NON_FIELD_ERRORS.
|
||||||
"""
|
"""
|
||||||
self.validate_unique()
|
pass
|
||||||
|
|
||||||
def validate_unique(self):
|
def validate_unique(self, exclude=None):
|
||||||
unique_checks, date_checks = self._get_unique_checks()
|
"""
|
||||||
|
Checks unique constraints on the model and raises ``ValidationError``
|
||||||
|
if any failed.
|
||||||
|
"""
|
||||||
|
unique_checks, date_checks = self._get_unique_checks(exclude=exclude)
|
||||||
|
|
||||||
errors = self._perform_unique_checks(unique_checks)
|
errors = self._perform_unique_checks(unique_checks)
|
||||||
date_errors = self._perform_date_checks(date_checks)
|
date_errors = self._perform_date_checks(date_checks)
|
||||||
|
@ -661,17 +665,35 @@ class Model(object):
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
||||||
def _get_unique_checks(self):
|
def _get_unique_checks(self, exclude=None):
|
||||||
from django.db.models.fields import FieldDoesNotExist, Field as ModelField
|
"""
|
||||||
|
Gather a list of checks to perform. Since validate_unique could be
|
||||||
|
called from a ModelForm, some fields may have been excluded; we can't
|
||||||
|
perform a unique check on a model that is missing fields involved
|
||||||
|
in that check.
|
||||||
|
Fields that did not validate should also be exluded, but they need
|
||||||
|
to be passed in via the exclude argument.
|
||||||
|
"""
|
||||||
|
if exclude is None:
|
||||||
|
exclude = []
|
||||||
|
unique_checks = []
|
||||||
|
for check in self._meta.unique_together:
|
||||||
|
for name in check:
|
||||||
|
# If this is an excluded field, don't add this check.
|
||||||
|
if name in exclude:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
unique_checks.append(check)
|
||||||
|
|
||||||
unique_checks = list(self._meta.unique_together)
|
# These are checks for the unique_for_<date/year/month>.
|
||||||
# these are checks for the unique_for_<date/year/month>
|
|
||||||
date_checks = []
|
date_checks = []
|
||||||
|
|
||||||
# Gather a list of checks for fields declared as unique and add them to
|
# Gather a list of checks for fields declared as unique and add them to
|
||||||
# the list of checks. Again, skip empty fields and any that did not validate.
|
# the list of checks.
|
||||||
for f in self._meta.fields:
|
for f in self._meta.fields:
|
||||||
name = f.name
|
name = f.name
|
||||||
|
if name in exclude:
|
||||||
|
continue
|
||||||
if f.unique:
|
if f.unique:
|
||||||
unique_checks.append((name,))
|
unique_checks.append((name,))
|
||||||
if f.unique_for_date:
|
if f.unique_for_date:
|
||||||
|
@ -682,7 +704,6 @@ class Model(object):
|
||||||
date_checks.append(('month', name, f.unique_for_month))
|
date_checks.append(('month', name, f.unique_for_month))
|
||||||
return unique_checks, date_checks
|
return unique_checks, date_checks
|
||||||
|
|
||||||
|
|
||||||
def _perform_unique_checks(self, unique_checks):
|
def _perform_unique_checks(self, unique_checks):
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
@ -779,34 +800,61 @@ class Model(object):
|
||||||
'field_label': unicode(field_labels)
|
'field_label': unicode(field_labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
def full_validate(self, exclude=[]):
|
def full_clean(self, exclude=None):
|
||||||
"""
|
"""
|
||||||
Cleans all fields and raises ValidationError containing message_dict
|
Calls clean_fields, clean, and validate_unique, on the model,
|
||||||
|
and raises a ``ValidationError`` for any errors that occured.
|
||||||
|
"""
|
||||||
|
errors = {}
|
||||||
|
if exclude is None:
|
||||||
|
exclude = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.clean_fields(exclude=exclude)
|
||||||
|
except ValidationError, e:
|
||||||
|
errors = e.update_error_dict(errors)
|
||||||
|
|
||||||
|
# Form.clean() is run even if other validation fails, so do the
|
||||||
|
# same with Model.clean() for consistency.
|
||||||
|
try:
|
||||||
|
self.clean()
|
||||||
|
except ValidationError, e:
|
||||||
|
errors = e.update_error_dict(errors)
|
||||||
|
|
||||||
|
# Run unique checks, but only for fields that passed validation.
|
||||||
|
for name in errors.keys():
|
||||||
|
if name != NON_FIELD_ERRORS and name not in exclude:
|
||||||
|
exclude.append(name)
|
||||||
|
try:
|
||||||
|
self.validate_unique(exclude=exclude)
|
||||||
|
except ValidationError, e:
|
||||||
|
errors = e.update_error_dict(errors)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise ValidationError(errors)
|
||||||
|
|
||||||
|
def clean_fields(self, exclude=None):
|
||||||
|
"""
|
||||||
|
Cleans all fields and raises a ValidationError containing message_dict
|
||||||
of all validation errors if any occur.
|
of all validation errors if any occur.
|
||||||
"""
|
"""
|
||||||
|
if exclude is None:
|
||||||
|
exclude = []
|
||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
for f in self._meta.fields:
|
for f in self._meta.fields:
|
||||||
if f.name in exclude:
|
if f.name in exclude:
|
||||||
continue
|
continue
|
||||||
|
# Skip validation for empty fields with blank=True. The developer
|
||||||
|
# is responsible for making sure they have a valid value.
|
||||||
|
raw_value = getattr(self, f.attname)
|
||||||
|
if f.blank and raw_value in validators.EMPTY_VALUES:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
setattr(self, f.attname, f.clean(getattr(self, f.attname), self))
|
setattr(self, f.attname, f.clean(raw_value, self))
|
||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
errors[f.name] = e.messages
|
errors[f.name] = e.messages
|
||||||
|
|
||||||
# Form.clean() is run even if other validation fails, so do the
|
|
||||||
# same with Model.validate() for consistency.
|
|
||||||
try:
|
|
||||||
self.validate()
|
|
||||||
except ValidationError, e:
|
|
||||||
if hasattr(e, 'message_dict'):
|
|
||||||
if errors:
|
|
||||||
for k, v in e.message_dict.items():
|
|
||||||
errors.setdefault(k, []).extend(v)
|
|
||||||
else:
|
|
||||||
errors = e.message_dict
|
|
||||||
else:
|
|
||||||
errors[NON_FIELD_ERRORS] = e.messages
|
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
||||||
|
|
|
@ -740,6 +740,11 @@ class ForeignKey(RelatedField, Field):
|
||||||
def validate(self, value, model_instance):
|
def validate(self, value, model_instance):
|
||||||
if self.rel.parent_link:
|
if self.rel.parent_link:
|
||||||
return
|
return
|
||||||
|
# Don't validate the field if a value wasn't supplied. This is
|
||||||
|
# generally the case when saving new inlines in the admin.
|
||||||
|
# See #12507.
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
super(ForeignKey, self).validate(value, model_instance)
|
super(ForeignKey, self).validate(value, model_instance)
|
||||||
if not value:
|
if not value:
|
||||||
return
|
return
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django.utils.datastructures import SortedDict
|
||||||
from django.utils.text import get_text_list, capfirst
|
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
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, UnresolvableValidationError
|
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
||||||
from django.core.validators import EMPTY_VALUES
|
from django.core.validators import EMPTY_VALUES
|
||||||
from util import ErrorList
|
from util import ErrorList
|
||||||
from forms import BaseForm, get_declared_fields
|
from forms import BaseForm, get_declared_fields
|
||||||
|
@ -250,31 +250,51 @@ class BaseModelForm(BaseForm):
|
||||||
super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
|
super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
|
||||||
error_class, label_suffix, empty_permitted)
|
error_class, label_suffix, empty_permitted)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_validation_exclusions(self):
|
||||||
|
"""
|
||||||
|
For backwards-compatibility, several types of fields need to be
|
||||||
|
excluded from model validation. See the following tickets for
|
||||||
|
details: #12507, #12521, #12553
|
||||||
|
"""
|
||||||
|
exclude = []
|
||||||
|
# Build up a list of fields that should be excluded from model field
|
||||||
|
# validation and unique checks.
|
||||||
|
for f in self.instance._meta.fields:
|
||||||
|
field = f.name
|
||||||
|
# Exclude fields that aren't on the form. The developer may be
|
||||||
|
# adding these values to the model after form validation.
|
||||||
|
if field not in self.fields:
|
||||||
|
exclude.append(f.name)
|
||||||
|
# Exclude fields that failed form validation. There's no need for
|
||||||
|
# the model fields to validate them as well.
|
||||||
|
elif field in self._errors.keys():
|
||||||
|
exclude.append(f.name)
|
||||||
|
# Exclude empty fields that are not required by the form. The
|
||||||
|
# underlying model field may be required, so this keeps the model
|
||||||
|
# field from raising that error.
|
||||||
|
else:
|
||||||
|
form_field = self.fields[field]
|
||||||
|
field_value = self.cleaned_data.get(field, None)
|
||||||
|
if field_value is None and not form_field.required:
|
||||||
|
exclude.append(f.name)
|
||||||
|
return exclude
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
opts = self._meta
|
opts = self._meta
|
||||||
self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
|
self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
|
||||||
|
exclude = self._get_validation_exclusions()
|
||||||
try:
|
try:
|
||||||
self.instance.full_validate(exclude=self._errors.keys())
|
self.instance.full_clean(exclude=exclude)
|
||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
for k, v in e.message_dict.items():
|
for k, v in e.message_dict.items():
|
||||||
if k != NON_FIELD_ERRORS:
|
if k != NON_FIELD_ERRORS:
|
||||||
self._errors.setdefault(k, ErrorList()).extend(v)
|
self._errors.setdefault(k, ErrorList()).extend(v)
|
||||||
|
|
||||||
# Remove the data from the cleaned_data dict since it was invalid
|
# Remove the data from the cleaned_data dict since it was invalid
|
||||||
if k in self.cleaned_data:
|
if k in self.cleaned_data:
|
||||||
del self.cleaned_data[k]
|
del self.cleaned_data[k]
|
||||||
|
|
||||||
if NON_FIELD_ERRORS in e.message_dict:
|
if NON_FIELD_ERRORS in e.message_dict:
|
||||||
raise ValidationError(e.message_dict[NON_FIELD_ERRORS])
|
raise ValidationError(e.message_dict[NON_FIELD_ERRORS])
|
||||||
|
|
||||||
# If model validation threw errors for fields that aren't on the
|
|
||||||
# form, the the errors cannot be corrected by the user. Displaying
|
|
||||||
# those errors would be pointless, so raise another type of
|
|
||||||
# exception that *won't* be caught and displayed by the form.
|
|
||||||
if set(e.message_dict.keys()) - set(self.fields.keys() + [NON_FIELD_ERRORS]):
|
|
||||||
raise UnresolvableValidationError(e.message_dict)
|
|
||||||
|
|
||||||
|
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
@ -412,17 +432,20 @@ class BaseModelFormSet(BaseFormSet):
|
||||||
self.validate_unique()
|
self.validate_unique()
|
||||||
|
|
||||||
def validate_unique(self):
|
def validate_unique(self):
|
||||||
# Iterate over the forms so that we can find one with potentially valid
|
# Collect unique_checks and date_checks to run from all the forms.
|
||||||
# data from which to extract the error checks
|
all_unique_checks = set()
|
||||||
|
all_date_checks = set()
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
if hasattr(form, 'cleaned_data'):
|
if not hasattr(form, 'cleaned_data'):
|
||||||
break
|
continue
|
||||||
else:
|
exclude = form._get_validation_exclusions()
|
||||||
return
|
unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
|
||||||
unique_checks, date_checks = form.instance._get_unique_checks()
|
all_unique_checks = all_unique_checks.union(set(unique_checks))
|
||||||
|
all_date_checks = all_date_checks.union(set(date_checks))
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
# Do each of the unique checks (unique and unique_together)
|
# Do each of the unique checks (unique and unique_together)
|
||||||
for unique_check in unique_checks:
|
for unique_check in all_unique_checks:
|
||||||
seen_data = set()
|
seen_data = set()
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
# if the form doesn't have cleaned_data then we ignore it,
|
# if the form doesn't have cleaned_data then we ignore it,
|
||||||
|
@ -444,7 +467,7 @@ class BaseModelFormSet(BaseFormSet):
|
||||||
# mark the data as seen
|
# mark the data as seen
|
||||||
seen_data.add(row_data)
|
seen_data.add(row_data)
|
||||||
# iterate over each of the date checks now
|
# iterate over each of the date checks now
|
||||||
for date_check in date_checks:
|
for date_check in all_date_checks:
|
||||||
seen_data = set()
|
seen_data = set()
|
||||||
lookup, field, unique_for = date_check
|
lookup, field, unique_for = date_check
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
|
|
|
@ -34,31 +34,88 @@ Validating objects
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
.. versionadded:: 1.2
|
||||||
|
|
||||||
To validate your model, call its ``full_validate()`` method:
|
There are three steps in validating a model, and all three are called by a
|
||||||
|
model's ``full_clean()`` method. Most of the time, this method will be called
|
||||||
|
automatically by a ``ModelForm``. (See the :ref:`ModelForm documentation
|
||||||
|
<topics-forms-modelforms>` for more information.) You should only need to call
|
||||||
|
``full_clean()`` if you plan to handle validation errors yourself.
|
||||||
|
|
||||||
.. method:: Model.full_validate([exclude=[]])
|
.. method:: Model.full_clean(exclude=None)
|
||||||
|
|
||||||
The optional ``exclude`` argument can contain a list of field names to omit
|
This method calls ``Model.clean_fields()``, ``Model.clean()``, and
|
||||||
when validating. This method raises ``ValidationError`` containing a
|
``Model.validate_unique()``, in that order and raises a ``ValidationError``
|
||||||
message dictionary with errors from all fields.
|
that has a ``message_dict`` attribute containing errors from all three stages.
|
||||||
|
|
||||||
To add your own validation logic, override the supplied ``validate()`` method:
|
The optional ``exclude`` argument can be used to provide a list of field names
|
||||||
|
that can be excluded from validation and cleaning. ``ModelForm`` uses this
|
||||||
|
argument to exclude fields that aren't present on your form from being
|
||||||
|
validated since any errors raised could not be corrected by the user.
|
||||||
|
|
||||||
Note that ``full_validate`` will NOT be called automatically when you call
|
Note that ``full_clean()`` will NOT be called automatically when you call
|
||||||
your model's ``save()`` method. You'll need to call it manually if you want
|
your model's ``save()`` method. You'll need to call it manually if you want
|
||||||
to run your model validators. (This is for backwards compatibility.) However,
|
to run model validation outside of a ``ModelForm``. (This is for backwards
|
||||||
if you're using a ``ModelForm``, it will call ``full_validate`` for you and
|
compatibility.)
|
||||||
will present any errors along with the other form error messages.
|
|
||||||
|
|
||||||
.. method:: Model.validate()
|
Example::
|
||||||
|
|
||||||
The ``validate()`` method on ``Model`` by default checks for uniqueness of
|
try:
|
||||||
fields and group of fields that are declared to be unique, so remember to call
|
article.full_validate()
|
||||||
``self.validate_unique()`` or the superclass' ``validate`` method if you want
|
except ValidationError, e:
|
||||||
this validation to run.
|
# Do something based on the errors contained in e.error_dict.
|
||||||
|
# Display them to a user, or handle them programatically.
|
||||||
|
|
||||||
|
The first step ``full_clean()`` performs is to clean each individual field.
|
||||||
|
|
||||||
|
.. method:: Model.clean_fields(exclude=None)
|
||||||
|
|
||||||
|
This method will validate all fields on your model. The optional ``exclude``
|
||||||
|
argument lets you provide a list of field names to exclude from validation. It
|
||||||
|
will raise a ``ValidationError`` if any fields fail validation.
|
||||||
|
|
||||||
|
The second step ``full_clean()`` performs is to call ``Model.clean()``.
|
||||||
|
This method should be overridden to perform custom validation on your model.
|
||||||
|
|
||||||
|
.. method:: Model.clean()
|
||||||
|
|
||||||
|
This method should be used to provide custom model validation, and to modify
|
||||||
|
attributes on your model if desired. For instance, you could use it to
|
||||||
|
automatically provide a value for a field, or to do validation that requires
|
||||||
|
access to more than a single field::
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
# Don't allow draft entries to have a pub_date.
|
||||||
|
if self.status == 'draft' and self.pub_date is not None:
|
||||||
|
raise ValidationError('Draft entries may not have a publication date.')
|
||||||
|
# Set the pub_date for published items if it hasn't been set already.
|
||||||
|
if self.status == 'published' and self.pub_date is None:
|
||||||
|
self.pub_date = datetime.datetime.now()
|
||||||
|
|
||||||
|
Any ``ValidationError`` raised by ``Model.clean()`` will be stored under a
|
||||||
|
special key that is used for errors that are tied to the entire model instead
|
||||||
|
of to a specific field. You can access these errors with ``NON_FIELD_ERRORS``::
|
||||||
|
|
||||||
|
|
||||||
|
from django.core.validators import ValidationError, NON_FIELD_ERRORS
|
||||||
|
try:
|
||||||
|
article.full_clean():
|
||||||
|
except ValidationError, e:
|
||||||
|
non_field_errors = e.message_dict[NON_FIELD_ERRORS]
|
||||||
|
|
||||||
|
Finally, ``full_clean()`` will check any unique constraints on your model.
|
||||||
|
|
||||||
|
.. method:: Model.validate_unique(exclude=None)
|
||||||
|
|
||||||
|
This method is similar to ``clean_fields``, but validates all uniqueness
|
||||||
|
constraints on your model instead of individual field values. The optional
|
||||||
|
``exclude`` argument allows you to provide a list of field names to exclude
|
||||||
|
from validation. It will raise a ``ValidationError`` if any fields fail
|
||||||
|
validation.
|
||||||
|
|
||||||
|
Note that if you provide an ``exclude`` argument to ``validate_unique``, any
|
||||||
|
``unique_together`` constraint that contains one of the fields you provided
|
||||||
|
will not be checked.
|
||||||
|
|
||||||
Any ``ValidationError`` raised in this method will be included in the
|
|
||||||
``message_dict`` under ``NON_FIELD_ERRORS``.
|
|
||||||
|
|
||||||
Saving objects
|
Saving objects
|
||||||
==============
|
==============
|
||||||
|
|
|
@ -515,6 +515,18 @@ There are a couple of things to note, however.
|
||||||
Chances are these notes won't affect you unless you're trying to do something
|
Chances are these notes won't affect you unless you're trying to do something
|
||||||
tricky with subclassing.
|
tricky with subclassing.
|
||||||
|
|
||||||
|
Interaction with model validation
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
As part of its validation process, ``ModelForm`` will call the ``clean()``
|
||||||
|
method of each field on your model that has a corresponding field on your form.
|
||||||
|
If you have excluded any model fields, validation will not be run on those
|
||||||
|
fields. See the :ref:`form validation <ref-forms-validation>` documentation
|
||||||
|
for more on how field cleaning and validation work. Also, your model's
|
||||||
|
``clean()`` method will be called before any uniqueness checks are made. See
|
||||||
|
:ref:`Validating objects <validating-objects>` for more information on the
|
||||||
|
model's ``clean()`` hook.
|
||||||
|
|
||||||
.. _model-formsets:
|
.. _model-formsets:
|
||||||
|
|
||||||
Model formsets
|
Model formsets
|
||||||
|
|
|
@ -1135,6 +1135,15 @@ True
|
||||||
|
|
||||||
>>> instance.delete()
|
>>> instance.delete()
|
||||||
|
|
||||||
|
# Test the non-required FileField
|
||||||
|
>>> f = TextFileForm(data={'description': u'Assistance'})
|
||||||
|
>>> f.fields['file'].required = False
|
||||||
|
>>> f.is_valid()
|
||||||
|
True
|
||||||
|
>>> instance = f.save()
|
||||||
|
>>> instance.file
|
||||||
|
<FieldFile: None>
|
||||||
|
|
||||||
>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
|
>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
|
||||||
>>> f.is_valid()
|
>>> f.is_valid()
|
||||||
True
|
True
|
||||||
|
@ -1446,16 +1455,41 @@ False
|
||||||
>>> form._errors
|
>>> form._errors
|
||||||
{'__all__': [u'Price with this Price and Quantity already exists.']}
|
{'__all__': [u'Price with this Price and Quantity already exists.']}
|
||||||
|
|
||||||
# This form is never valid because quantity is blank=False.
|
This Price instance generated by this form is not valid because the quantity
|
||||||
|
field is required, but the form is valid because the field is excluded from
|
||||||
|
the form. This is for backwards compatibility.
|
||||||
|
|
||||||
>>> class PriceForm(ModelForm):
|
>>> class PriceForm(ModelForm):
|
||||||
... class Meta:
|
... class Meta:
|
||||||
... model = Price
|
... model = Price
|
||||||
... exclude = ('quantity',)
|
... exclude = ('quantity',)
|
||||||
>>> form = PriceForm({'price': '6.00'})
|
>>> form = PriceForm({'price': '6.00'})
|
||||||
>>> form.is_valid()
|
>>> form.is_valid()
|
||||||
|
True
|
||||||
|
>>> price = form.save(commit=False)
|
||||||
|
>>> price.full_clean()
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
UnresolvableValidationError: {'quantity': [u'This field cannot be null.']}
|
ValidationError: {'quantity': [u'This field cannot be null.']}
|
||||||
|
|
||||||
|
The form should not validate fields that it doesn't contain even if they are
|
||||||
|
specified using 'fields', not 'exclude'.
|
||||||
|
... class Meta:
|
||||||
|
... model = Price
|
||||||
|
... fields = ('price',)
|
||||||
|
>>> form = PriceForm({'price': '6.00'})
|
||||||
|
>>> form.is_valid()
|
||||||
|
True
|
||||||
|
|
||||||
|
The form should still have an instance of a model that is not complete and
|
||||||
|
not saved into a DB yet.
|
||||||
|
|
||||||
|
>>> form.instance.price
|
||||||
|
Decimal('6.00')
|
||||||
|
>>> form.instance.quantity is None
|
||||||
|
True
|
||||||
|
>>> form.instance.pk is None
|
||||||
|
True
|
||||||
|
|
||||||
# Unique & unique together with null values
|
# Unique & unique together with null values
|
||||||
>>> class BookForm(ModelForm):
|
>>> class BookForm(ModelForm):
|
||||||
|
|
|
@ -543,6 +543,10 @@ This is used in the admin for save_as functionality.
|
||||||
... 'book_set-2-title': '',
|
... 'book_set-2-title': '',
|
||||||
... }
|
... }
|
||||||
|
|
||||||
|
>>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
|
||||||
|
>>> formset.is_valid()
|
||||||
|
True
|
||||||
|
|
||||||
>>> new_author = Author.objects.create(name='Charles Baudelaire')
|
>>> new_author = Author.objects.create(name='Charles Baudelaire')
|
||||||
>>> formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True)
|
>>> formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True)
|
||||||
>>> [book for book in formset.save() if book.author.pk == new_author.pk]
|
>>> [book for book in formset.save() if book.author.pk == new_author.pk]
|
||||||
|
@ -1031,6 +1035,19 @@ False
|
||||||
>>> formset._non_form_errors
|
>>> formset._non_form_errors
|
||||||
[u'Please correct the duplicate data for price and quantity, which must be unique.']
|
[u'Please correct the duplicate data for price and quantity, which must be unique.']
|
||||||
|
|
||||||
|
# Only the price field is specified, this should skip any unique checks since
|
||||||
|
# the unique_together is not fulfilled. This will fail with a KeyError if broken.
|
||||||
|
>>> FormSet = modelformset_factory(Price, fields=("price",), extra=2)
|
||||||
|
>>> data = {
|
||||||
|
... 'form-TOTAL_FORMS': '2',
|
||||||
|
... 'form-INITIAL_FORMS': '0',
|
||||||
|
... 'form-0-price': '24',
|
||||||
|
... 'form-1-price': '24',
|
||||||
|
... }
|
||||||
|
>>> formset = FormSet(data)
|
||||||
|
>>> formset.is_valid()
|
||||||
|
True
|
||||||
|
|
||||||
>>> FormSet = inlineformset_factory(Author, Book, extra=0)
|
>>> FormSet = inlineformset_factory(Author, Book, extra=0)
|
||||||
>>> author = Author.objects.order_by('id')[0]
|
>>> author = Author.objects.order_by('id')[0]
|
||||||
>>> book_ids = author.book_set.values_list('id', flat=True)
|
>>> book_ids = author.book_set.values_list('id', flat=True)
|
||||||
|
|
|
@ -17,8 +17,8 @@ class ModelToValidate(models.Model):
|
||||||
url = models.URLField(blank=True)
|
url = models.URLField(blank=True)
|
||||||
f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe])
|
f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe])
|
||||||
|
|
||||||
def validate(self):
|
def clean(self):
|
||||||
super(ModelToValidate, self).validate()
|
super(ModelToValidate, self).clean()
|
||||||
if self.number == 11:
|
if self.number == 11:
|
||||||
raise ValidationError('Invalid number supplied!')
|
raise ValidationError('Invalid number supplied!')
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class UniqueTogetherModel(models.Model):
|
||||||
efield = models.EmailField()
|
efield = models.EmailField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = (('ifield', 'cfield',),('ifield', 'efield'), )
|
unique_together = (('ifield', 'cfield',), ('ifield', 'efield'))
|
||||||
|
|
||||||
class UniqueForDateModel(models.Model):
|
class UniqueForDateModel(models.Model):
|
||||||
start_date = models.DateField()
|
start_date = models.DateField()
|
||||||
|
@ -51,3 +51,15 @@ class CustomMessagesModel(models.Model):
|
||||||
error_messages={'null': 'NULL', 'not42': 'AAARGH', 'not_equal': '%s != me'},
|
error_messages={'null': 'NULL', 'not42': 'AAARGH', 'not_equal': '%s != me'},
|
||||||
validators=[validate_answer_to_universe]
|
validators=[validate_answer_to_universe]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Author(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
class Article(models.Model):
|
||||||
|
title = models.CharField(max_length=100)
|
||||||
|
author = models.ForeignKey(Author)
|
||||||
|
pub_date = models.DateTimeField(blank=True)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.pub_date is None:
|
||||||
|
self.pub_date = datetime.now()
|
||||||
|
|
|
@ -5,9 +5,9 @@ from models import CustomMessagesModel
|
||||||
class CustomMessagesTest(ValidationTestCase):
|
class CustomMessagesTest(ValidationTestCase):
|
||||||
def test_custom_simple_validator_message(self):
|
def test_custom_simple_validator_message(self):
|
||||||
cmm = CustomMessagesModel(number=12)
|
cmm = CustomMessagesModel(number=12)
|
||||||
self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['AAARGH'])
|
self.assertFieldFailsValidationWithMessage(cmm.full_clean, 'number', ['AAARGH'])
|
||||||
|
|
||||||
def test_custom_null_message(self):
|
def test_custom_null_message(self):
|
||||||
cmm = CustomMessagesModel()
|
cmm = CustomMessagesModel()
|
||||||
self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['NULL'])
|
self.assertFieldFailsValidationWithMessage(cmm.full_clean, 'number', ['NULL'])
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
import datetime
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from models import CustomPKModel, UniqueTogetherModel, UniqueFieldsModel, UniqueForDateModel, ModelToValidate
|
from models import CustomPKModel, UniqueTogetherModel, UniqueFieldsModel, UniqueForDateModel, ModelToValidate
|
||||||
|
@ -26,8 +27,8 @@ class GetUniqueCheckTests(unittest.TestCase):
|
||||||
def test_unique_for_date_gets_picked_up(self):
|
def test_unique_for_date_gets_picked_up(self):
|
||||||
m = UniqueForDateModel()
|
m = UniqueForDateModel()
|
||||||
self.assertEqual((
|
self.assertEqual((
|
||||||
[('id',)],
|
[('id',)],
|
||||||
[('date', 'count', 'start_date'), ('year', 'count', 'end_date'), ('month', 'order', 'end_date')]
|
[('date', 'count', 'start_date'), ('year', 'count', 'end_date'), ('month', 'order', 'end_date')]
|
||||||
), m._get_unique_checks()
|
), m._get_unique_checks()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,12 +48,13 @@ class PerformUniqueChecksTest(unittest.TestCase):
|
||||||
l = len(connection.queries)
|
l = len(connection.queries)
|
||||||
mtv = ModelToValidate(number=10, name='Some Name')
|
mtv = ModelToValidate(number=10, name='Some Name')
|
||||||
setattr(mtv, '_adding', True)
|
setattr(mtv, '_adding', True)
|
||||||
mtv.full_validate()
|
mtv.full_clean()
|
||||||
self.assertEqual(l+1, len(connection.queries))
|
self.assertEqual(l+1, len(connection.queries))
|
||||||
|
|
||||||
def test_primary_key_unique_check_not_performed_when_not_adding(self):
|
def test_primary_key_unique_check_not_performed_when_not_adding(self):
|
||||||
"""Regression test for #12132"""
|
"""Regression test for #12132"""
|
||||||
l = len(connection.queries)
|
l = len(connection.queries)
|
||||||
mtv = ModelToValidate(number=10, name='Some Name')
|
mtv = ModelToValidate(number=10, name='Some Name')
|
||||||
mtv.full_validate()
|
mtv.full_clean()
|
||||||
self.assertEqual(l, len(connection.queries))
|
self.assertEqual(l, len(connection.queries))
|
||||||
|
|
||||||
|
|
|
@ -1,58 +1,107 @@
|
||||||
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
from django import forms
|
||||||
from django.db import models
|
from django.test import TestCase
|
||||||
|
from django.core.exceptions import NON_FIELD_ERRORS
|
||||||
from modeltests.validation import ValidationTestCase
|
from modeltests.validation import ValidationTestCase
|
||||||
from models import *
|
from modeltests.validation.models import Author, Article, ModelToValidate
|
||||||
|
|
||||||
from validators import TestModelsWithValidators
|
# Import other tests for this package.
|
||||||
from test_unique import GetUniqueCheckTests, PerformUniqueChecksTest
|
from modeltests.validation.validators import TestModelsWithValidators
|
||||||
from test_custom_messages import CustomMessagesTest
|
from modeltests.validation.test_unique import GetUniqueCheckTests, PerformUniqueChecksTest
|
||||||
|
from modeltests.validation.test_custom_messages import CustomMessagesTest
|
||||||
|
|
||||||
|
|
||||||
class BaseModelValidationTests(ValidationTestCase):
|
class BaseModelValidationTests(ValidationTestCase):
|
||||||
|
|
||||||
def test_missing_required_field_raises_error(self):
|
def test_missing_required_field_raises_error(self):
|
||||||
mtv = ModelToValidate(f_with_custom_validator=42)
|
mtv = ModelToValidate(f_with_custom_validator=42)
|
||||||
self.assertFailsValidation(mtv.full_validate, ['name', 'number'])
|
self.assertFailsValidation(mtv.full_clean, ['name', 'number'])
|
||||||
|
|
||||||
def test_with_correct_value_model_validates(self):
|
def test_with_correct_value_model_validates(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name')
|
mtv = ModelToValidate(number=10, name='Some Name')
|
||||||
self.assertEqual(None, mtv.full_validate())
|
self.assertEqual(None, mtv.full_clean())
|
||||||
|
|
||||||
def test_custom_validate_method_is_called(self):
|
def test_custom_validate_method(self):
|
||||||
mtv = ModelToValidate(number=11)
|
mtv = ModelToValidate(number=11)
|
||||||
self.assertFailsValidation(mtv.full_validate, [NON_FIELD_ERRORS, 'name'])
|
self.assertFailsValidation(mtv.full_clean, [NON_FIELD_ERRORS, 'name'])
|
||||||
|
|
||||||
def test_wrong_FK_value_raises_error(self):
|
def test_wrong_FK_value_raises_error(self):
|
||||||
mtv=ModelToValidate(number=10, name='Some Name', parent_id=3)
|
mtv=ModelToValidate(number=10, name='Some Name', parent_id=3)
|
||||||
self.assertFailsValidation(mtv.full_validate, ['parent'])
|
self.assertFailsValidation(mtv.full_clean, ['parent'])
|
||||||
|
|
||||||
def test_correct_FK_value_validates(self):
|
def test_correct_FK_value_validates(self):
|
||||||
parent = ModelToValidate.objects.create(number=10, name='Some Name')
|
parent = ModelToValidate.objects.create(number=10, name='Some Name')
|
||||||
mtv=ModelToValidate(number=10, name='Some Name', parent_id=parent.pk)
|
mtv=ModelToValidate(number=10, name='Some Name', parent_id=parent.pk)
|
||||||
self.assertEqual(None, mtv.full_validate())
|
self.assertEqual(None, mtv.full_clean())
|
||||||
|
|
||||||
def test_wrong_email_value_raises_error(self):
|
def test_wrong_email_value_raises_error(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name', email='not-an-email')
|
mtv = ModelToValidate(number=10, name='Some Name', email='not-an-email')
|
||||||
self.assertFailsValidation(mtv.full_validate, ['email'])
|
self.assertFailsValidation(mtv.full_clean, ['email'])
|
||||||
|
|
||||||
def test_correct_email_value_passes(self):
|
def test_correct_email_value_passes(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name', email='valid@email.com')
|
mtv = ModelToValidate(number=10, name='Some Name', email='valid@email.com')
|
||||||
self.assertEqual(None, mtv.full_validate())
|
self.assertEqual(None, mtv.full_clean())
|
||||||
|
|
||||||
def test_wrong_url_value_raises_error(self):
|
def test_wrong_url_value_raises_error(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name', url='not a url')
|
mtv = ModelToValidate(number=10, name='Some Name', url='not a url')
|
||||||
self.assertFieldFailsValidationWithMessage(mtv.full_validate, 'url', [u'Enter a valid value.'])
|
self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'Enter a valid value.'])
|
||||||
|
|
||||||
def test_correct_url_but_nonexisting_gives_404(self):
|
def test_correct_url_but_nonexisting_gives_404(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name', url='http://google.com/we-love-microsoft.html')
|
mtv = ModelToValidate(number=10, name='Some Name', url='http://google.com/we-love-microsoft.html')
|
||||||
self.assertFieldFailsValidationWithMessage(mtv.full_validate, 'url', [u'This URL appears to be a broken link.'])
|
self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.'])
|
||||||
|
|
||||||
def test_correct_url_value_passes(self):
|
def test_correct_url_value_passes(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name', url='http://www.djangoproject.com/')
|
mtv = ModelToValidate(number=10, name='Some Name', url='http://www.djangoproject.com/')
|
||||||
self.assertEqual(None, mtv.full_validate()) # This will fail if there's no Internet connection
|
self.assertEqual(None, mtv.full_clean()) # This will fail if there's no Internet connection
|
||||||
|
|
||||||
def test_text_greater_that_charfields_max_length_eaises_erros(self):
|
def test_text_greater_that_charfields_max_length_eaises_erros(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name'*100)
|
mtv = ModelToValidate(number=10, name='Some Name'*100)
|
||||||
self.assertFailsValidation(mtv.full_validate, ['name',])
|
self.assertFailsValidation(mtv.full_clean, ['name',])
|
||||||
|
|
||||||
|
class ArticleForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Article
|
||||||
|
exclude = ['author']
|
||||||
|
|
||||||
|
class ModelFormsTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.author = Author.objects.create(name='Joseph Kocherhans')
|
||||||
|
|
||||||
|
def test_partial_validation(self):
|
||||||
|
# Make sure the "commit=False and set field values later" idiom still
|
||||||
|
# works with model validation.
|
||||||
|
data = {
|
||||||
|
'title': 'The state of model validation',
|
||||||
|
'pub_date': '2010-1-10 14:49:00'
|
||||||
|
}
|
||||||
|
form = ArticleForm(data)
|
||||||
|
self.assertEqual(form.errors.keys(), [])
|
||||||
|
article = form.save(commit=False)
|
||||||
|
article.author = self.author
|
||||||
|
article.save()
|
||||||
|
|
||||||
|
def test_validation_with_empty_blank_field(self):
|
||||||
|
# Since a value for pub_date wasn't provided and the field is
|
||||||
|
# blank=True, model-validation should pass.
|
||||||
|
# Also, Article.clean() should be run, so pub_date will be filled after
|
||||||
|
# validation, so the form should save cleanly even though pub_date is
|
||||||
|
# not allowed to be null.
|
||||||
|
data = {
|
||||||
|
'title': 'The state of model validation',
|
||||||
|
}
|
||||||
|
article = Article(author_id=self.author.id)
|
||||||
|
form = ArticleForm(data, instance=article)
|
||||||
|
self.assertEqual(form.errors.keys(), [])
|
||||||
|
self.assertNotEqual(form.instance.pub_date, None)
|
||||||
|
article = form.save()
|
||||||
|
|
||||||
|
def test_validation_with_invalid_blank_field(self):
|
||||||
|
# Even though pub_date is set to blank=True, an invalid value was
|
||||||
|
# provided, so it should fail validation.
|
||||||
|
data = {
|
||||||
|
'title': 'The state of model validation',
|
||||||
|
'pub_date': 'never'
|
||||||
|
}
|
||||||
|
article = Article(author_id=self.author.id)
|
||||||
|
form = ArticleForm(data, instance=article)
|
||||||
|
self.assertEqual(form.errors.keys(), ['pub_date'])
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,13 @@ from models import *
|
||||||
class TestModelsWithValidators(ValidationTestCase):
|
class TestModelsWithValidators(ValidationTestCase):
|
||||||
def test_custom_validator_passes_for_correct_value(self):
|
def test_custom_validator_passes_for_correct_value(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42)
|
mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42)
|
||||||
self.assertEqual(None, mtv.full_validate())
|
self.assertEqual(None, mtv.full_clean())
|
||||||
|
|
||||||
def test_custom_validator_raises_error_for_incorrect_value(self):
|
def test_custom_validator_raises_error_for_incorrect_value(self):
|
||||||
mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=12)
|
mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=12)
|
||||||
self.assertFailsValidation(mtv.full_validate, ['f_with_custom_validator'])
|
self.assertFailsValidation(mtv.full_clean, ['f_with_custom_validator'])
|
||||||
self.assertFieldFailsValidationWithMessage(
|
self.assertFieldFailsValidationWithMessage(
|
||||||
mtv.full_validate,
|
mtv.full_clean,
|
||||||
'f_with_custom_validator',
|
'f_with_custom_validator',
|
||||||
[u'This is not the answer to life, universe and everything!']
|
[u'This is not the answer to life, universe and everything!']
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,8 @@ import re
|
||||||
import datetime
|
import datetime
|
||||||
from django.core.files import temp as tempfile
|
from django.core.files import temp as tempfile
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import User, Permission
|
from django.contrib.auth import admin # Register auth models with the admin.
|
||||||
|
from django.contrib.auth.models import User, Permission, UNUSABLE_PASSWORD
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.admin.models import LogEntry, DELETION
|
from django.contrib.admin.models import LogEntry, DELETION
|
||||||
from django.contrib.admin.sites import LOGIN_FORM_KEY
|
from django.contrib.admin.sites import LOGIN_FORM_KEY
|
||||||
|
@ -1766,3 +1767,37 @@ class ReadonlyTest(TestCase):
|
||||||
self.assertEqual(Post.objects.count(), 2)
|
self.assertEqual(Post.objects.count(), 2)
|
||||||
p = Post.objects.order_by('-id')[0]
|
p = Post.objects.order_by('-id')[0]
|
||||||
self.assertEqual(p.posted, datetime.date.today())
|
self.assertEqual(p.posted, datetime.date.today())
|
||||||
|
|
||||||
|
class IncompleteFormTest(TestCase):
|
||||||
|
"""
|
||||||
|
Tests validation of a ModelForm that doesn't explicitly have all data
|
||||||
|
corresponding to model fields. Model validation shouldn't fail
|
||||||
|
such a forms.
|
||||||
|
"""
|
||||||
|
fixtures = ['admin-views-users.xml']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client.login(username='super', password='secret')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
def test_user_creation(self):
|
||||||
|
response = self.client.post('/test_admin/admin/auth/user/add/', {
|
||||||
|
'username': 'newuser',
|
||||||
|
'password1': 'newpassword',
|
||||||
|
'password2': 'newpassword',
|
||||||
|
})
|
||||||
|
new_user = User.objects.order_by('-id')[0]
|
||||||
|
self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
|
||||||
|
self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD)
|
||||||
|
|
||||||
|
def test_password_mismatch(self):
|
||||||
|
response = self.client.post('/test_admin/admin/auth/user/add/', {
|
||||||
|
'username': 'newuser',
|
||||||
|
'password1': 'newpassword',
|
||||||
|
'password2': 'mismatch',
|
||||||
|
})
|
||||||
|
self.assertEquals(response.status_code, 200)
|
||||||
|
self.assert_('password' not in response.context['form'].errors)
|
||||||
|
self.assertFormError(response, 'form', 'password2', ["The two password fields didn't match."])
|
||||||
|
|
|
@ -81,7 +81,7 @@ class DeletionTests(TestCase):
|
||||||
regression for #10750
|
regression for #10750
|
||||||
"""
|
"""
|
||||||
# exclude some required field from the forms
|
# exclude some required field from the forms
|
||||||
ChildFormSet = inlineformset_factory(School, Child)
|
ChildFormSet = inlineformset_factory(School, Child, exclude=['father', 'mother'])
|
||||||
school = School.objects.create(name=u'test')
|
school = School.objects.create(name=u'test')
|
||||||
mother = Parent.objects.create(name=u'mother')
|
mother = Parent.objects.create(name=u'mother')
|
||||||
father = Parent.objects.create(name=u'father')
|
father = Parent.objects.create(name=u'father')
|
||||||
|
@ -89,13 +89,13 @@ class DeletionTests(TestCase):
|
||||||
'child_set-TOTAL_FORMS': u'1',
|
'child_set-TOTAL_FORMS': u'1',
|
||||||
'child_set-INITIAL_FORMS': u'0',
|
'child_set-INITIAL_FORMS': u'0',
|
||||||
'child_set-0-name': u'child',
|
'child_set-0-name': u'child',
|
||||||
'child_set-0-mother': unicode(mother.pk),
|
|
||||||
'child_set-0-father': unicode(father.pk),
|
|
||||||
}
|
}
|
||||||
formset = ChildFormSet(data, instance=school)
|
formset = ChildFormSet(data, instance=school)
|
||||||
self.assertEqual(formset.is_valid(), True)
|
self.assertEqual(formset.is_valid(), True)
|
||||||
objects = formset.save(commit=False)
|
objects = formset.save(commit=False)
|
||||||
self.assertEqual(school.child_set.count(), 0)
|
for obj in objects:
|
||||||
objects[0].save()
|
obj.mother = mother
|
||||||
|
obj.father = father
|
||||||
|
obj.save()
|
||||||
self.assertEqual(school.child_set.count(), 1)
|
self.assertEqual(school.child_set.count(), 1)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue