Author: jkocherhans
Date: 2010-01-11 20:29:45 -0600 (Mon, 11 Jan 2010)
New Revision: 12206
Modified:
django/trunk/django/contrib/admin/options.py
django/trunk/django/contrib/auth/forms.py
django/trunk/django/core/exceptions.py
django/trunk/django/db/models/base.py
django/trunk/django/db/models/fields/related.py
django/trunk/django/forms/models.py
django/trunk/docs/ref/models/instances.txt
django/trunk/docs/topics/forms/modelforms.txt
django/trunk/tests/modeltests/model_forms/models.py
django/trunk/tests/modeltests/model_formsets/models.py
django/trunk/tests/modeltests/validation/models.py
django/trunk/tests/modeltests/validation/test_custom_messages.py
django/trunk/tests/modeltests/validation/test_unique.py
django/trunk/tests/modeltests/validation/tests.py
django/trunk/tests/modeltests/validation/validators.py
django/trunk/tests/regressiontests/admin_views/tests.py
django/trunk/tests/regressiontests/inline_formsets/tests.py
Log:
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.
Modified: django/trunk/django/contrib/admin/options.py
===================================================================
--- django/trunk/django/contrib/admin/options.py 2010-01-11 09:25:10 UTC
(rev 12205)
+++ django/trunk/django/contrib/admin/options.py 2010-01-12 02:29:45 UTC
(rev 12206)
@@ -579,12 +579,12 @@
"""
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
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):
"""
@@ -758,11 +758,7 @@
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
- # Save the object, even if inline formsets haven't been
- # 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)
+ new_object = self.save_form(request, form, change=False)
form_validated = True
else:
form_validated = False
@@ -779,15 +775,13 @@
prefix=prefix,
queryset=inline.queryset(request))
formsets.append(formset)
if all_valid(formsets) and form_validated:
+ self.save_model(request, new_object, form, change=False)
+ form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=False)
self.log_addition(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:
# Prepare the dict of initial data from the request.
# We have to special-case M2Ms as a list of comma-separated PKs.
Modified: django/trunk/django/contrib/auth/forms.py
===================================================================
--- django/trunk/django/contrib/auth/forms.py 2010-01-11 09:25:10 UTC (rev
12205)
+++ django/trunk/django/contrib/auth/forms.py 2010-01-12 02:29:45 UTC (rev
12206)
@@ -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.tokens import default_token_generator
from django.contrib.sites.models import Site
@@ -21,12 +21,6 @@
model = User
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):
username = self.cleaned_data["username"]
try:
@@ -40,9 +34,15 @@
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't
match."))
- self.instance.set_password(password1)
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):
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)."),
Modified: django/trunk/django/core/exceptions.py
===================================================================
--- django/trunk/django/core/exceptions.py 2010-01-11 09:25:10 UTC (rev
12205)
+++ django/trunk/django/core/exceptions.py 2010-01-12 02:29:45 UTC (rev
12206)
@@ -33,7 +33,7 @@
pass
NON_FIELD_ERRORS = '__all__'
-class BaseValidationError(Exception):
+class ValidationError(Exception):
"""An error while validating data."""
def __init__(self, message, code=None, params=None):
import operator
@@ -64,10 +64,14 @@
return repr(self.message_dict)
return repr(self.messages)
-class ValidationError(BaseValidationError):
- pass
+ def update_error_dict(self, error_dict):
+ if hasattr(self, 'message_dict'):
+ if error_dict:
+ for k, v in self.message_dict.items():
+ error_dict.setdefault(k, []).extend(v)
+ else:
+ error_dict = self.message_dict
+ else:
+ error_dict[NON_FIELD_ERRORS] = self.messages
+ return error_dict
-class UnresolvableValidationError(BaseValidationError):
- """Validation error that cannot be resolved by the user."""
- pass
-
Modified: django/trunk/django/db/models/base.py
===================================================================
--- django/trunk/django/db/models/base.py 2010-01-11 09:25:10 UTC (rev
12205)
+++ django/trunk/django/db/models/base.py 2010-01-12 02:29:45 UTC (rev
12206)
@@ -640,17 +640,21 @@
def prepare_database_save(self, unused):
return self.pk
- def validate(self):
+ def clean(self):
"""
Hook for doing any extra model-wide validation after clean() has been
- called on every field. Any ValidationError raised by this method will
- not be associated with a particular field; it will have a special-case
- association with the field defined by NON_FIELD_ERRORS.
+ called on every field by self.clean_fields. Any ValidationError raised
+ by this method will not be associated with a particular field; it will
+ have a special-case association with the field defined by
NON_FIELD_ERRORS.
"""
- self.validate_unique()
+ pass
- def validate_unique(self):
- unique_checks, date_checks = self._get_unique_checks()
+ def validate_unique(self, exclude=None):
+ """
+ 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)
date_errors = self._perform_date_checks(date_checks)
@@ -661,17 +665,35 @@
if errors:
raise ValidationError(errors)
- def _get_unique_checks(self):
- from django.db.models.fields import FieldDoesNotExist, Field as
ModelField
+ def _get_unique_checks(self, exclude=None):
+ """
+ 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 = []
# 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:
name = f.name
+ if name in exclude:
+ continue
if f.unique:
unique_checks.append((name,))
if f.unique_for_date:
@@ -682,7 +704,6 @@
date_checks.append(('month', name, f.unique_for_month))
return unique_checks, date_checks
-
def _perform_unique_checks(self, unique_checks):
errors = {}
@@ -779,34 +800,61 @@
'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.
"""
+ if exclude is None:
+ exclude = []
+
errors = {}
for f in self._meta.fields:
if f.name in exclude:
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:
- setattr(self, f.attname, f.clean(getattr(self, f.attname),
self))
+ setattr(self, f.attname, f.clean(raw_value, self))
except ValidationError, e:
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:
raise ValidationError(errors)
Modified: django/trunk/django/db/models/fields/related.py
===================================================================
--- django/trunk/django/db/models/fields/related.py 2010-01-11 09:25:10 UTC
(rev 12205)
+++ django/trunk/django/db/models/fields/related.py 2010-01-12 02:29:45 UTC
(rev 12206)
@@ -740,6 +740,11 @@
def validate(self, value, model_instance):
if self.rel.parent_link:
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)
if not value:
return
Modified: django/trunk/django/forms/models.py
===================================================================
--- django/trunk/django/forms/models.py 2010-01-11 09:25:10 UTC (rev 12205)
+++ django/trunk/django/forms/models.py 2010-01-12 02:29:45 UTC (rev 12206)
@@ -9,7 +9,7 @@
from django.utils.text import get_text_list, capfirst
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 util import ErrorList
from forms import BaseForm, get_declared_fields
@@ -250,31 +250,51 @@
super(BaseModelForm, self).__init__(data, files, auto_id, prefix,
object_data,
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):
opts = self._meta
self.instance = construct_instance(self, self.instance, opts.fields,
opts.exclude)
+ exclude = self._get_validation_exclusions()
try:
- self.instance.full_validate(exclude=self._errors.keys())
+ self.instance.full_clean(exclude=exclude)
except ValidationError, e:
for k, v in e.message_dict.items():
if k != NON_FIELD_ERRORS:
self._errors.setdefault(k, ErrorList()).extend(v)
-
# Remove the data from the cleaned_data dict since it was
invalid
if k in self.cleaned_data:
del self.cleaned_data[k]
-
if NON_FIELD_ERRORS in e.message_dict:
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
def save(self, commit=True):
@@ -412,17 +432,20 @@
self.validate_unique()
def validate_unique(self):
- # Iterate over the forms so that we can find one with potentially valid
- # data from which to extract the error checks
+ # Collect unique_checks and date_checks to run from all the forms.
+ all_unique_checks = set()
+ all_date_checks = set()
for form in self.forms:
- if hasattr(form, 'cleaned_data'):
- break
- else:
- return
- unique_checks, date_checks = form.instance._get_unique_checks()
+ if not hasattr(form, 'cleaned_data'):
+ continue
+ exclude = form._get_validation_exclusions()
+ unique_checks, date_checks =
form.instance._get_unique_checks(exclude=exclude)
+ all_unique_checks = all_unique_checks.union(set(unique_checks))
+ all_date_checks = all_date_checks.union(set(date_checks))
+
errors = []
# 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()
for form in self.forms:
# if the form doesn't have cleaned_data then we ignore it,
@@ -444,7 +467,7 @@
# mark the data as seen
seen_data.add(row_data)
# iterate over each of the date checks now
- for date_check in date_checks:
+ for date_check in all_date_checks:
seen_data = set()
lookup, field, unique_for = date_check
for form in self.forms:
Modified: django/trunk/docs/ref/models/instances.txt
===================================================================
--- django/trunk/docs/ref/models/instances.txt 2010-01-11 09:25:10 UTC (rev
12205)
+++ django/trunk/docs/ref/models/instances.txt 2010-01-12 02:29:45 UTC (rev
12206)
@@ -34,32 +34,89 @@
.. 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
-when validating. This method raises ``ValidationError`` containing a
-message dictionary with errors from all fields.
+This method calls ``Model.clean_fields()``, ``Model.clean()``, and
+``Model.validate_unique()``, in that order and raises a ``ValidationError``
+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
-to run your model validators. (This is for backwards compatibility.) However,
-if you're using a ``ModelForm``, it will call ``full_validate`` for you and
-will present any errors along with the other form error messages.
+to run model validation outside of a ``ModelForm``. (This is for backwards
+compatibility.)
-.. method:: Model.validate()
+Example::
-The ``validate()`` method on ``Model`` by default checks for uniqueness of
-fields and group of fields that are declared to be unique, so remember to call
-``self.validate_unique()`` or the superclass' ``validate`` method if you want
-this validation to run.
+ try:
+ article.full_validate()
+ except ValidationError, e:
+ # Do something based on the errors contained in e.error_dict.
+ # Display them to a user, or handle them programatically.
-Any ``ValidationError`` raised in this method will be included in the
-``message_dict`` under ``NON_FIELD_ERRORS``.
+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.
+
+
Saving objects
==============
Modified: django/trunk/docs/topics/forms/modelforms.txt
===================================================================
--- django/trunk/docs/topics/forms/modelforms.txt 2010-01-11 09:25:10 UTC
(rev 12205)
+++ django/trunk/docs/topics/forms/modelforms.txt 2010-01-12 02:29:45 UTC
(rev 12206)
@@ -515,6 +515,18 @@
Chances are these notes won't affect you unless you're trying to do something
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
Modified: django/trunk/tests/modeltests/model_forms/models.py
===================================================================
--- django/trunk/tests/modeltests/model_forms/models.py 2010-01-11 09:25:10 UTC
(rev 12205)
+++ django/trunk/tests/modeltests/model_forms/models.py 2010-01-12 02:29:45 UTC
(rev 12206)
@@ -1135,6 +1135,15 @@
>>> 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.is_valid()
True
@@ -1173,7 +1182,7 @@
>>> class BigIntForm(forms.ModelForm):
... class Meta:
... model = BigInt
-...
+...
>>> bif = BigIntForm({'biggie': '-9223372036854775808'})
>>> bif.is_valid()
True
@@ -1446,17 +1455,42 @@
>>> form._errors
{'__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 Meta:
... model = Price
... exclude = ('quantity',)
>>> form = PriceForm({'price': '6.00'})
>>> form.is_valid()
+True
+>>> price = form.save(commit=False)
+>>> price.full_clean()
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
>>> class BookForm(ModelForm):
... class Meta:
Modified: django/trunk/tests/modeltests/model_formsets/models.py
===================================================================
--- django/trunk/tests/modeltests/model_formsets/models.py 2010-01-11
09:25:10 UTC (rev 12205)
+++ django/trunk/tests/modeltests/model_formsets/models.py 2010-01-12
02:29:45 UTC (rev 12206)
@@ -543,6 +543,10 @@
... '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')
>>> formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True)
>>> [book for book in formset.save() if book.author.pk == new_author.pk]
@@ -1031,6 +1035,19 @@
>>> formset._non_form_errors
[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)
>>> author = Author.objects.order_by('id')[0]
>>> book_ids = author.book_set.values_list('id', flat=True)
Modified: django/trunk/tests/modeltests/validation/models.py
===================================================================
--- django/trunk/tests/modeltests/validation/models.py 2010-01-11 09:25:10 UTC
(rev 12205)
+++ django/trunk/tests/modeltests/validation/models.py 2010-01-12 02:29:45 UTC
(rev 12206)
@@ -17,8 +17,8 @@
url = models.URLField(blank=True)
f_with_custom_validator = models.IntegerField(blank=True, null=True,
validators=[validate_answer_to_universe])
- def validate(self):
- super(ModelToValidate, self).validate()
+ def clean(self):
+ super(ModelToValidate, self).clean()
if self.number == 11:
raise ValidationError('Invalid number supplied!')
@@ -36,7 +36,7 @@
efield = models.EmailField()
class Meta:
- unique_together = (('ifield', 'cfield',),('ifield', 'efield'), )
+ unique_together = (('ifield', 'cfield',), ('ifield', 'efield'))
class UniqueForDateModel(models.Model):
start_date = models.DateField()
@@ -51,3 +51,15 @@
error_messages={'null': 'NULL', 'not42': 'AAARGH', 'not_equal': '%s !=
me'},
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()
Modified: django/trunk/tests/modeltests/validation/test_custom_messages.py
===================================================================
--- django/trunk/tests/modeltests/validation/test_custom_messages.py
2010-01-11 09:25:10 UTC (rev 12205)
+++ django/trunk/tests/modeltests/validation/test_custom_messages.py
2010-01-12 02:29:45 UTC (rev 12206)
@@ -5,9 +5,9 @@
class CustomMessagesTest(ValidationTestCase):
def test_custom_simple_validator_message(self):
cmm = CustomMessagesModel(number=12)
- self.assertFieldFailsValidationWithMessage(cmm.full_validate,
'number', ['AAARGH'])
+ self.assertFieldFailsValidationWithMessage(cmm.full_clean, 'number',
['AAARGH'])
def test_custom_null_message(self):
cmm = CustomMessagesModel()
- self.assertFieldFailsValidationWithMessage(cmm.full_validate,
'number', ['NULL'])
+ self.assertFieldFailsValidationWithMessage(cmm.full_clean, 'number',
['NULL'])
Modified: django/trunk/tests/modeltests/validation/test_unique.py
===================================================================
--- django/trunk/tests/modeltests/validation/test_unique.py 2010-01-11
09:25:10 UTC (rev 12205)
+++ django/trunk/tests/modeltests/validation/test_unique.py 2010-01-12
02:29:45 UTC (rev 12206)
@@ -1,4 +1,5 @@
import unittest
+import datetime
from django.conf import settings
from django.db import connection
from models import CustomPKModel, UniqueTogetherModel, UniqueFieldsModel,
UniqueForDateModel, ModelToValidate
@@ -26,8 +27,8 @@
def test_unique_for_date_gets_picked_up(self):
m = UniqueForDateModel()
self.assertEqual((
- [('id',)],
- [('date', 'count', 'start_date'), ('year', 'count',
'end_date'), ('month', 'order', 'end_date')]
+ [('id',)],
+ [('date', 'count', 'start_date'), ('year', 'count', 'end_date'),
('month', 'order', 'end_date')]
), m._get_unique_checks()
)
@@ -47,12 +48,13 @@
l = len(connection.queries)
mtv = ModelToValidate(number=10, name='Some Name')
setattr(mtv, '_adding', True)
- mtv.full_validate()
+ mtv.full_clean()
self.assertEqual(l+1, len(connection.queries))
def test_primary_key_unique_check_not_performed_when_not_adding(self):
"""Regression test for #12132"""
l = len(connection.queries)
mtv = ModelToValidate(number=10, name='Some Name')
- mtv.full_validate()
+ mtv.full_clean()
self.assertEqual(l, len(connection.queries))
+
Modified: django/trunk/tests/modeltests/validation/tests.py
===================================================================
--- django/trunk/tests/modeltests/validation/tests.py 2010-01-11 09:25:10 UTC
(rev 12205)
+++ django/trunk/tests/modeltests/validation/tests.py 2010-01-12 02:29:45 UTC
(rev 12206)
@@ -1,58 +1,107 @@
-from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
-from django.db import models
-
+from django import forms
+from django.test import TestCase
+from django.core.exceptions import NON_FIELD_ERRORS
from modeltests.validation import ValidationTestCase
-from models import *
+from modeltests.validation.models import Author, Article, ModelToValidate
-from validators import TestModelsWithValidators
-from test_unique import GetUniqueCheckTests, PerformUniqueChecksTest
-from test_custom_messages import CustomMessagesTest
+# Import other tests for this package.
+from modeltests.validation.validators import TestModelsWithValidators
+from modeltests.validation.test_unique import GetUniqueCheckTests,
PerformUniqueChecksTest
+from modeltests.validation.test_custom_messages import CustomMessagesTest
class BaseModelValidationTests(ValidationTestCase):
def test_missing_required_field_raises_error(self):
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):
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)
- 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):
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):
parent = ModelToValidate.objects.create(number=10, name='Some Name')
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):
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):
mtv = ModelToValidate(number=10, name='Some Name',
email='[email protected]')
- self.assertEqual(None, mtv.full_validate())
+ self.assertEqual(None, mtv.full_clean())
def test_wrong_url_value_raises_error(self):
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):
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):
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):
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'])
+
Modified: django/trunk/tests/modeltests/validation/validators.py
===================================================================
--- django/trunk/tests/modeltests/validation/validators.py 2010-01-11
09:25:10 UTC (rev 12205)
+++ django/trunk/tests/modeltests/validation/validators.py 2010-01-12
02:29:45 UTC (rev 12206)
@@ -6,13 +6,13 @@
class TestModelsWithValidators(ValidationTestCase):
def test_custom_validator_passes_for_correct_value(self):
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):
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(
- mtv.full_validate,
+ mtv.full_clean,
'f_with_custom_validator',
[u'This is not the answer to life, universe and everything!']
)
Modified: django/trunk/tests/regressiontests/admin_views/tests.py
===================================================================
--- django/trunk/tests/regressiontests/admin_views/tests.py 2010-01-11
09:25:10 UTC (rev 12205)
+++ django/trunk/tests/regressiontests/admin_views/tests.py 2010-01-12
02:29:45 UTC (rev 12206)
@@ -4,7 +4,8 @@
import datetime
from django.core.files import temp as tempfile
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.admin.models import LogEntry, DELETION
from django.contrib.admin.sites import LOGIN_FORM_KEY
@@ -1766,3 +1767,37 @@
self.assertEqual(Post.objects.count(), 2)
p = Post.objects.order_by('-id')[0]
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."])
Modified: django/trunk/tests/regressiontests/inline_formsets/tests.py
===================================================================
--- django/trunk/tests/regressiontests/inline_formsets/tests.py 2010-01-11
09:25:10 UTC (rev 12205)
+++ django/trunk/tests/regressiontests/inline_formsets/tests.py 2010-01-12
02:29:45 UTC (rev 12206)
@@ -81,7 +81,7 @@
regression for #10750
"""
# 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')
mother = Parent.objects.create(name=u'mother')
father = Parent.objects.create(name=u'father')
@@ -89,13 +89,13 @@
'child_set-TOTAL_FORMS': u'1',
'child_set-INITIAL_FORMS': u'0',
'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)
self.assertEqual(formset.is_valid(), True)
objects = formset.save(commit=False)
- self.assertEqual(school.child_set.count(), 0)
- objects[0].save()
+ for obj in objects:
+ obj.mother = mother
+ obj.father = father
+ obj.save()
self.assertEqual(school.child_set.count(), 1)
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en.