Ticket #12521: 12521.4.diff

File 12521.4.diff, 42.1 KB (added by jkocherhans, 14 years ago)

A rework based on conversations with Honza

  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index 6dc707e..8f68eb1 100644
    a b class ModelAdmin(BaseModelAdmin):  
    579579        """
    580580        messages.info(request, message)
    581581
    582     def save_form(self, request, form, change, commit=False):
     582    def save_form(self, request, form, change):
    583583        """
    584584        Given a ModelForm return an unsaved instance. ``change`` is True if
    585585        the object is being changed, and False if it's being added.
    586586        """
    587         return form.save(commit=commit)
     587        return form.save(commit=False)
    588588
    589589    def save_model(self, request, obj, form, change):
    590590        """
    class ModelAdmin(BaseModelAdmin):  
    758758        if request.method == 'POST':
    759759            form = ModelForm(request.POST, request.FILES)
    760760            if form.is_valid():
    761                 # Save the object, even if inline formsets haven't been
    762                 # validated yet. We need to pass the valid model to the
    763                 # formsets for validation. If the formsets do not validate, we
    764                 # will delete the object.
    765                 new_object = self.save_form(request, form, change=False, commit=True)
     761                new_object = self.save_form(request, form, change=False)
    766762                form_validated = True
    767763            else:
    768764                form_validated = False
    class ModelAdmin(BaseModelAdmin):  
    779775                                  prefix=prefix, queryset=inline.queryset(request))
    780776                formsets.append(formset)
    781777            if all_valid(formsets) and form_validated:
     778                self.save_model(request, new_object, form, change=False)
     779                form.save_m2m()
    782780                for formset in formsets:
    783781                    self.save_formset(request, form, formset, change=False)
    784782
    785783                self.log_addition(request, new_object)
    786784                return self.response_add(request, new_object)
    787             elif form_validated:
    788                 # The form was valid, but formsets were not, so delete the
    789                 # object we saved above.
    790                 new_object.delete()
    791785        else:
    792786            # Prepare the dict of initial data from the request.
    793787            # We have to special-case M2Ms as a list of comma-separated PKs.
  • django/contrib/auth/forms.py

    diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
    index dbc55ca..55e770e 100644
    a b  
    1 from django.contrib.auth.models import User, UNUSABLE_PASSWORD
     1from django.contrib.auth.models import User
    22from django.contrib.auth import authenticate
    33from django.contrib.auth.tokens import default_token_generator
    44from django.contrib.sites.models import Site
    class UserCreationForm(forms.ModelForm):  
    2121        model = User
    2222        fields = ("username",)
    2323
    24     def clean(self):
    25         # Fill the password field so model validation won't complain about it
    26         # being blank. We'll set it with the real value below.
    27         self.instance.password = UNUSABLE_PASSWORD
    28         super(UserCreationForm, self).clean()
    29 
    3024    def clean_username(self):
    3125        username = self.cleaned_data["username"]
    3226        try:
    class UserCreationForm(forms.ModelForm):  
    4034        password2 = self.cleaned_data["password2"]
    4135        if password1 != password2:
    4236            raise forms.ValidationError(_("The two password fields didn't match."))
    43         self.instance.set_password(password1)
    4437        return password2
    4538
     39    def save(self, commit=True):
     40        user = super(UserCreationForm, self).save(commit=False)
     41        user.set_password(self.cleaned_data["password1"])
     42        if commit:
     43            user.save()
     44        return user
     45
    4646class UserChangeForm(forms.ModelForm):
    4747    username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$',
    4848        help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."),
  • django/core/exceptions.py

    diff --git a/django/core/exceptions.py b/django/core/exceptions.py
    index fee7db4..bc599df 100644
    a b class FieldError(Exception):  
    3333    pass
    3434
    3535NON_FIELD_ERRORS = '__all__'
    36 class BaseValidationError(Exception):
     36class ValidationError(Exception):
    3737    """An error while validating data."""
    3838    def __init__(self, message, code=None, params=None):
    3939        import operator
    class BaseValidationError(Exception):  
    6464            return repr(self.message_dict)
    6565        return repr(self.messages)
    6666
    67 class ValidationError(BaseValidationError):
    68     pass
    69 
    70 class UnresolvableValidationError(BaseValidationError):
    71     """Validation error that cannot be resolved by the user."""
    72     pass
     67def update_errors(errors, e):
     68    if hasattr(e, 'message_dict'):
     69        if errors:
     70            for k, v in e.message_dict.items():
     71                errors.setdefault(k, []).extend(v)
     72        else:
     73            errors = e.message_dict
     74    else:
     75        errors[NON_FIELD_ERRORS] = e.messages
     76    return errors
    7377
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 06db7cc..bce3979 100644
    a b import sys  
    33import os
    44from itertools import izip
    55import django.db.models.manager     # Imported to register signal handler.
    6 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS
     6from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS, update_errors
    77from django.core import validators
    88from django.db.models.fields import AutoField, FieldDoesNotExist
    99from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
    class Model(object):  
    642642    def prepare_database_save(self, unused):
    643643        return self.pk
    644644
    645     def validate(self):
     645    def clean(self):
    646646        """
    647647        Hook for doing any extra model-wide validation after clean() has been
    648         called on every field. Any ValidationError raised by this method will
    649         not be associated with a particular field; it will have a special-case
    650         association with the field defined by NON_FIELD_ERRORS.
     648        called on every field by self.clean_fields. Any ValidationError raised
     649        by this method will not be associated with a particular field; it will
     650        have a special-case association with the field defined by NON_FIELD_ERRORS.
    651651        """
    652         self.validate_unique()
     652        pass
    653653
    654     def validate_unique(self):
    655         unique_checks, date_checks = self._get_unique_checks()
     654    def validate_unique(self, exclude=None):
     655        """
     656        Checks unique constraints on the model and raises ``ValidationError``
     657        if any failed.
     658        """
     659        unique_checks, date_checks = self._get_unique_checks(exclude=exclude)
    656660
    657661        errors = self._perform_unique_checks(unique_checks)
    658662        date_errors = self._perform_date_checks(date_checks)
    class Model(object):  
    663667        if errors:
    664668            raise ValidationError(errors)
    665669
    666     def _get_unique_checks(self):
    667         from django.db.models.fields import FieldDoesNotExist, Field as ModelField
     670    def _get_unique_checks(self, exclude=None):
     671        """
     672        Gather a list of checks to perform. Since validate_unique could be
     673        called from a ModelForm, some fields may have been excluded; we can't
     674        perform a unique check on a model that is missing fields involved
     675        in that check.
     676        Fields that did not validate should also be exluded, but they need
     677        to be passed in via the exclude argument.
     678        """
     679        if exclude is None:
     680            exclude = []
     681        unique_checks = []
     682        for check in self._meta.unique_together:
     683            for name in check:
     684                # If this is an excluded field, don't add this check.
     685                if name in exclude:
     686                    break
     687            else:
     688                unique_checks.append(check)
    668689
    669         unique_checks = list(self._meta.unique_together)
    670         # these are checks for the unique_for_<date/year/month>
     690        # These are checks for the unique_for_<date/year/month>.
    671691        date_checks = []
    672692
    673693        # Gather a list of checks for fields declared as unique and add them to
    674         # the list of checks. Again, skip empty fields and any that did not validate.
     694        # the list of checks.
    675695        for f in self._meta.fields:
    676696            name = f.name
     697            if name in exclude:
     698                continue
    677699            if f.unique:
    678700                unique_checks.append((name,))
    679701            if f.unique_for_date:
    class Model(object):  
    684706                date_checks.append(('month', name, f.unique_for_month))
    685707        return unique_checks, date_checks
    686708
    687 
    688709    def _perform_unique_checks(self, unique_checks):
    689710        errors = {}
    690711
    class Model(object):  
    781802                'field_label': unicode(field_labels)
    782803            }
    783804
    784     def full_validate(self, exclude=[]):
     805    def full_clean(self, exclude=None):
     806        """
     807        Calls clean_fields, clean, and validate_unique, on the model,
     808        and raises a ``ValidationError`` for any errors that occured.
     809        """
     810        errors = {}
     811        if exclude is None:
     812            exclude = []
     813
     814        try:
     815            self.clean_fields(exclude=exclude)
     816        except ValidationError, e:
     817            errors = update_errors(errors, e)
     818
     819        # Form.clean() is run even if other validation fails, so do the
     820        # same with Model.clean() for consistency.
     821        try:
     822            self.clean()
     823        except ValidationError, e:
     824            errors = update_errors(errors, e)
     825
     826        # Run unique checks, but only for fields that passed validation.
     827        for name in errors.keys():
     828            if name != NON_FIELD_ERRORS and name not in exclude:
     829                exclude.append(name)
     830        try:
     831            self.validate_unique(exclude=exclude)
     832        except ValidationError, e:
     833            errors = update_errors(errors, e)
     834
     835        if errors:
     836            raise ValidationError(errors)
     837
     838    def clean_fields(self, exclude=None):
    785839        """
    786         Cleans all fields and raises ValidationError containing message_dict
     840        Cleans all fields and raises a ValidationError containing message_dict
    787841        of all validation errors if any occur.
    788842        """
     843        if exclude is None:
     844            exclude = []
    789845        errors = {}
    790846        for f in self._meta.fields:
    791847            if f.name in exclude:
    792848                continue
     849            # Skip validation for empty fields with blank=True. The developer
     850            # is responsible for making sure they have a valid value.
     851            raw_value = getattr(self, f.attname)
     852            if f.blank and raw_value in validators.EMPTY_VALUES:
     853                continue
    793854            try:
    794                 setattr(self, f.attname, f.clean(getattr(self, f.attname), self))
     855                setattr(self, f.attname, f.clean(raw_value, self))
    795856            except ValidationError, e:
    796857                errors[f.name] = e.messages
    797 
    798         # Form.clean() is run even if other validation fails, so do the
    799         # same with Model.validate() for consistency.
    800         try:
    801             self.validate()
    802         except ValidationError, e:
    803             if hasattr(e, 'message_dict'):
    804                 if errors:
    805                     for k, v in e.message_dict.items():
    806                         errors.set_default(k, []).extend(v)
    807                 else:
    808                     errors = e.message_dict
    809             else:
    810                 errors[NON_FIELD_ERRORS] = e.messages
    811 
    812858        if errors:
    813859            raise ValidationError(errors)
    814860
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index 8fec836..ae6a2f7 100644
    a b class ForeignKey(RelatedField, Field):  
    740740    def validate(self, value, model_instance):
    741741        if self.rel.parent_link:
    742742            return
     743        # Don't validate the field if a value wasn't supplied. This is
     744        # generally the case when saving new inlines in the admin.
     745        # See #12507.
     746        if value is None:
     747            return
    743748        super(ForeignKey, self).validate(value, model_instance)
    744749        if not value:
    745750            return
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index ff20c93..0085afd 100644
    a b from django.utils.datastructures import SortedDict  
    99from django.utils.text import get_text_list, capfirst
    1010from django.utils.translation import ugettext_lazy as _, ugettext
    1111
    12 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, UnresolvableValidationError
     12from django.core.exceptions import ValidationError, update_errors, NON_FIELD_ERRORS
    1313from django.core.validators import EMPTY_VALUES
    1414from util import ErrorList
    1515from forms import BaseForm, get_declared_fields
    class ModelFormMetaclass(type):  
    228228class BaseModelForm(BaseForm):
    229229    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
    230230                 initial=None, error_class=ErrorList, label_suffix=':',
    231                  empty_permitted=False, instance=None):
     231                 empty_permitted=False, instance=None, validate_model=False):
    232232        opts = self._meta
     233        self.validate_model = validate_model
    233234        if instance is None:
    234235            # if we didn't get an instance, instantiate a new one
    235236            self.instance = opts.model()
    class BaseModelForm(BaseForm):  
    245246        super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
    246247                                            error_class, label_suffix, empty_permitted)
    247248
     249
     250    def _get_validation_exclusions(self):
     251        """
     252        For backwards-compatibility, several types of fields need to be
     253        excluded from model validation. See the following tickets for
     254        details: #12507, #12521, #12553
     255        However, if self.full_validate=True, run complete model validation.
     256        """
     257        exclude = []
     258        if self.validate_model:
     259            return exclude
     260
     261        # Build up a list of fields that should be excluded from model field
     262        # validation and unique checks.
     263        for f in self.instance._meta.fields:
     264            field = f.name
     265            # Exclude fields that aren't on the form. The developer may be
     266            # adding these values to the model after form validation.
     267            if field not in self.fields:
     268                exclude.append(f.name)
     269            # Exclude fields that failed form validation. There's no need for
     270            # the model fields to validate them as well.
     271            elif field in self._errors.keys():
     272                exclude.append(f.name)
     273            # Exclude empty fields that are not required by the form. The
     274            # underlying model field may be required, so this keeps the model
     275            # field from raising that error.
     276            else:
     277                form_field = self.fields[field]
     278                field_value = self.cleaned_data.get(field, None)
     279                if field_value is None and not form_field.required:
     280                    exclude.append(f.name)
     281        return exclude
     282
    248283    def clean(self):
    249284        opts = self._meta
    250285        self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
     286        exclude = self._get_validation_exclusions()
    251287        try:
    252             self.instance.full_validate(exclude=self._errors.keys())
     288            self.instance.full_clean(exclude=exclude)
    253289        except ValidationError, e:
    254290            for k, v in e.message_dict.items():
    255291                if k != NON_FIELD_ERRORS:
    256292                    self._errors.setdefault(k, ErrorList()).extend(v)
    257 
    258293                    # Remove the data from the cleaned_data dict since it was invalid
    259294                    if k in self.cleaned_data:
    260295                        del self.cleaned_data[k]
    261 
    262296            if NON_FIELD_ERRORS in e.message_dict:
    263297                raise ValidationError(e.message_dict[NON_FIELD_ERRORS])
    264 
    265             # If model validation threw errors for fields that aren't on the
    266             # form, the the errors cannot be corrected by the user. Displaying
    267             # those errors would be pointless, so raise another type of
    268             # exception that *won't* be caught and displayed by the form.
    269             if set(e.message_dict.keys()) - set(self.fields.keys() + [NON_FIELD_ERRORS]):
    270                 raise UnresolvableValidationError(e.message_dict)
    271 
    272 
    273298        return self.cleaned_data
    274299
    275300    def save(self, commit=True):
    class BaseModelFormSet(BaseFormSet):  
    407432        self.validate_unique()
    408433
    409434    def validate_unique(self):
    410         # Iterate over the forms so that we can find one with potentially valid
    411         # data from which to extract the error checks
     435        # Collect unique_checks and date_checks to run from all the forms.
     436        all_unique_checks = set()
     437        all_date_checks = set()
    412438        for form in self.forms:
    413             if hasattr(form, 'cleaned_data'):
    414                 break
    415         else:
    416             return
    417         unique_checks, date_checks = form.instance._get_unique_checks()
     439            if not hasattr(form, 'cleaned_data'):
     440                continue
     441            exclude = form._get_validation_exclusions()
     442            unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
     443            all_unique_checks = all_unique_checks.union(set(unique_checks))
     444            all_date_checks = all_date_checks.union(set(date_checks))
     445
    418446        errors = []
    419447        # Do each of the unique checks (unique and unique_together)
    420         for unique_check in unique_checks:
     448        for unique_check in all_unique_checks:
    421449            seen_data = set()
    422450            for form in self.forms:
    423451                # if the form doesn't have cleaned_data then we ignore it,
    class BaseModelFormSet(BaseFormSet):  
    439467                    # mark the data as seen
    440468                    seen_data.add(row_data)
    441469        # iterate over each of the date checks now
    442         for date_check in date_checks:
     470        for date_check in all_date_checks:
    443471            seen_data = set()
    444472            lookup, field, unique_for = date_check
    445473            for form in self.forms:
  • docs/ref/models/instances.txt

    diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
    index 9b720be..92f5543 100644
    a b Validating objects  
    3434
    3535.. versionadded:: 1.2
    3636
    37 To validate your model, call its ``full_validate()`` method:
     37To add your own validation logic to a model, override the supplied ``clean()``
     38method.
    3839
    39 .. method:: Model.full_validate([exclude=[]])
     40.. method:: Model.clean()
    4041
    41 The optional ``exclude`` argument can contain a list of field names to omit
    42 when validating. This method raises ``ValidationError`` containing a
     42This method should be used to provide model-wide validation, and to modify
     43attributes on your model if desired. For instance, you could use it to
     44automatically provide a value for a field, or to do validation that requires
     45access to more than a single field::
     46
     47    def clean(self):
     48        # Don't allow draft entries to have a pub_date.
     49        if self.status == 'draft' and self.pub_date is not None:
     50            raise ValidationError('Draft entries may not have a publication date.')
     51        # Set the pub_date for published items if it hasn't been set already.
     52        if self.status == 'published' and self.pub_date is None:
     53            self.pub_date = datetime.datetime.now()
     54
     55When this method is called by ``full_clean()``, any ``ValidationError`` raised
     56will be included in the ``message_dict`` under ``NON_FIELD_ERRORS``.
     57
     58To validate and clean your model, call its ``full_clean()`` method:
     59
     60.. method:: Model.full_clean(exclude=None)
     61
     62This method calls ``Model.clean_fields()``, ``Model.validate_unique()`` and
     63``Model.clean()`` in that order and raises a ``ValidationError`` containing a
    4364message dictionary with errors from all fields.
    4465
    45 To add your own validation logic, override the supplied ``validate()`` method:
     66The optional ``exclude`` argument can be used to provide a list of field names
     67that can be excluded from validation and cleaning.
    4668
    47 Note that ``full_validate`` will NOT be called automatically when you call
     69Note that ``full_clean()`` will NOT be called automatically when you call
    4870your model's ``save()`` method. You'll need to call it manually if you want
    49 to run your model validators. (This is for backwards compatibility.) However,
    50 if you're using a ``ModelForm``, it will call ``full_validate`` for you and
    51 will present any errors along with the other form error messages.
     71to run model validation. (This is for backwards compatibility.)
     72
     73You will only need to use this method if you need to customize error display
     74instead of just using a :ref:`ModelForm <topics-forms-modelforms>`
     75
     76Example::
     77
     78    try:
     79        article.full_validate()
     80    except ValidationError, e:
     81        # Do something based on the errors contained in e.error_dict.
     82        # Display them to a user, or handle them programatically.
     83
     84.. method:: Model.clean_fields(exclude=None)
     85
     86This method will validate all fields on your model. The optional ``exclude``
     87argument lets you provide a list of field names to exclude from validation. It
     88will raise a ``ValidationError`` if any fields fail validation.
     89
     90.. method:: Model.validate_unique(exclude=None)
    5291
    53 .. method:: Model.validate()
     92This method is similar to ``clean_fields``, but validates all uniqueness
     93constraints on your model instead of individual field values. The optional
     94``exclude`` argument allows you to provide a list of field names to exclude
     95from validation. It will raise a ``ValidationError`` if any fields fail
     96validation.
    5497
    55 The ``validate()`` method on ``Model`` by default checks for uniqueness of
    56 fields and group of fields that are declared to be unique, so remember to call
    57 ``self.validate_unique()`` or the superclass' ``validate`` method if you want
    58 this validation to run.
     98Note that if you provide an ``exclude`` argument to ``validate_unique``, any
     99``unique_together`` constraint that contains one of the fields you provided
     100will not be checked.
    59101
    60 Any ``ValidationError`` raised in this method will be included in the
    61 ``message_dict`` under ``NON_FIELD_ERRORS``.
    62102
    63103Saving objects
    64104==============
  • tests/modeltests/model_forms/models.py

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index ba59f9a..401f620 100644
    a b True  
    11141114
    11151115>>> instance.delete()
    11161116
     1117# Test the non-required FileField
     1118>>> f = TextFileForm(data={'description': u'Assistance'})
     1119>>> f.fields['file'].required = False
     1120>>> f.is_valid()
     1121True
     1122>>> instance = f.save()
     1123>>> instance.file
     1124<FieldFile: None>
     1125
    11171126>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
    11181127>>> f.is_valid()
    11191128True
    True  
    11521161>>> class BigIntForm(forms.ModelForm):
    11531162...     class Meta:
    11541163...         model = BigInt
    1155 ... 
     1164...
    11561165>>> bif = BigIntForm({'biggie': '-9223372036854775808'})
    11571166>>> bif.is_valid()
    11581167True
    False  
    14251434>>> form._errors
    14261435{'__all__': [u'Price with this Price and Quantity already exists.']}
    14271436
    1428 # This form is never valid because quantity is blank=False.
     1437This Price instance generated by this form is not valid because the quantity
     1438field is required, but the form is valid because the field is excluded from
     1439the form. This is for backwards compatibility.
     1440
    14291441>>> class PriceForm(ModelForm):
    14301442...     class Meta:
    14311443...         model = Price
    14321444...         exclude = ('quantity',)
    14331445>>> form = PriceForm({'price': '6.00'})
    14341446>>> form.is_valid()
     1447True
     1448>>> price = form.save(commit=False)
     1449>>> price.full_clean()
    14351450Traceback (most recent call last):
    14361451  ...
    1437 UnresolvableValidationError: {'quantity': [u'This field cannot be null.']}
     1452ValidationError: {'quantity': [u'This field cannot be null.']}
     1453
     1454The form should not validate fields that it doesn't contain even if they are
     1455specified using 'fields', not 'exclude'.
     1456...     class Meta:
     1457...         model = Price
     1458...         fields = ('price',)
     1459>>> form = PriceForm({'price': '6.00'})
     1460>>> form.is_valid()
     1461True
     1462
     1463The form should still have an instance of a model that is not complete and
     1464not saved into a DB yet.
     1465
     1466>>> form.instance.price
     1467Decimal('6.00')
     1468>>> form.instance.quantity is None
     1469True
     1470>>> form.instance.pk is None
     1471True
    14381472
    14391473# Unique & unique together with null values
    14401474>>> class BookForm(ModelForm):
  • tests/modeltests/model_formsets/models.py

    diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
    index 5eab202..702523e 100644
    a b This is used in the admin for save_as functionality.  
    543543...     'book_set-2-title': '',
    544544... }
    545545
     546>>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
     547>>> formset.is_valid()
     548True
     549
    546550>>> new_author = Author.objects.create(name='Charles Baudelaire')
    547551>>> formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True)
    548552>>> [book for book in formset.save() if book.author.pk == new_author.pk]
    False  
    10311035>>> formset._non_form_errors
    10321036[u'Please correct the duplicate data for price and quantity, which must be unique.']
    10331037
     1038# Only the price field is specified, this should skip any unique checks since
     1039# the unique_together is not fulfilled. This will fail with a KeyError if broken.
     1040>>> FormSet = modelformset_factory(Price, fields=("price",), extra=2)
     1041>>> data = {
     1042...     'form-TOTAL_FORMS': '2',
     1043...     'form-INITIAL_FORMS': '0',
     1044...     'form-0-price': '24',
     1045...     'form-1-price': '24',
     1046... }
     1047>>> formset = FormSet(data)
     1048>>> formset.is_valid()
     1049True
     1050
    10341051>>> FormSet = inlineformset_factory(Author, Book, extra=0)
    10351052>>> author = Author.objects.order_by('id')[0]
    10361053>>> book_ids = author.book_set.values_list('id', flat=True)
  • tests/modeltests/validation/models.py

    diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
    index f1b0c51..017373c 100644
    a b class ModelToValidate(models.Model):  
    1717    url = models.URLField(blank=True)
    1818    f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe])
    1919
    20     def validate(self):
    21         super(ModelToValidate, self).validate()
     20    def clean(self):
     21        super(ModelToValidate, self).clean()
    2222        if self.number == 11:
    2323            raise ValidationError('Invalid number supplied!')
    2424
    class UniqueTogetherModel(models.Model):  
    3636    efield = models.EmailField()
    3737
    3838    class Meta:
    39         unique_together = (('ifield', 'cfield',),('ifield', 'efield'), )
     39        unique_together = (('ifield', 'cfield',), ('ifield', 'efield'))
    4040
    4141class UniqueForDateModel(models.Model):
    4242    start_date = models.DateField()
    class CustomMessagesModel(models.Model):  
    5151        error_messages={'null': 'NULL', 'not42': 'AAARGH', 'not_equal': '%s != me'},
    5252        validators=[validate_answer_to_universe]
    5353    )
     54
     55class Author(models.Model):
     56    name = models.CharField(max_length=100)
     57
     58class Article(models.Model):
     59    title = models.CharField(max_length=100)
     60    author = models.ForeignKey(Author)
     61    pub_date = models.DateTimeField(blank=True)
     62
     63    def clean(self):
     64        if self.pub_date is None:
     65            self.pub_date = datetime.now()
  • tests/modeltests/validation/test_custom_messages.py

    diff --git a/tests/modeltests/validation/test_custom_messages.py b/tests/modeltests/validation/test_custom_messages.py
    index 9a958a0..05bb651 100644
    a b from models import CustomMessagesModel  
    55class CustomMessagesTest(ValidationTestCase):
    66    def test_custom_simple_validator_message(self):
    77        cmm = CustomMessagesModel(number=12)
    8         self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['AAARGH'])
     8        self.assertFieldFailsValidationWithMessage(cmm.full_clean, 'number', ['AAARGH'])
    99
    1010    def test_custom_null_message(self):
    1111        cmm = CustomMessagesModel()
    12         self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['NULL'])
     12        self.assertFieldFailsValidationWithMessage(cmm.full_clean, 'number', ['NULL'])
    1313
  • tests/modeltests/validation/test_unique.py

    diff --git a/tests/modeltests/validation/test_unique.py b/tests/modeltests/validation/test_unique.py
    index cbb56aa..8b24def 100644
    a b  
    11import unittest
     2import datetime
    23from django.conf import settings
    34from django.db import connection
    45from models import CustomPKModel, UniqueTogetherModel, UniqueFieldsModel, UniqueForDateModel, ModelToValidate
    class GetUniqueCheckTests(unittest.TestCase):  
    1314        )
    1415
    1516    def test_unique_together_gets_picked_up(self):
    16         m = UniqueTogetherModel()
     17        # Give the fields values, otherwise they won't be picked up in
     18        # unique checks.
     19        m = UniqueTogetherModel(
     20            cfield='test',
     21            ifield=1,
     22            efield='test@example.com'
     23        )
    1724        self.assertEqual(
    1825            ([('ifield', 'cfield',),('ifield', 'efield'), ('id',), ], []),
    1926            m._get_unique_checks()
    class GetUniqueCheckTests(unittest.TestCase):  
    2431        self.assertEqual(([('my_pk_field',)], []), m._get_unique_checks())
    2532
    2633    def test_unique_for_date_gets_picked_up(self):
    27         m = UniqueForDateModel()
     34        # Give the fields values, otherwise they won't be picked up in
     35        # unique checks.
     36        m = UniqueForDateModel(
     37            start_date=datetime.date(2010, 1, 9),
     38            end_date=datetime.datetime(2010, 1, 9, 9, 0, 0),
     39            count=1,
     40            order=1,
     41            name='test'
     42        )
    2843        self.assertEqual((
    29                 [('id',)],
    30                 [('date', 'count', 'start_date'), ('year', 'count', 'end_date'), ('month', 'order', 'end_date')]
     44            [('id',)],
     45            [('date', 'count', 'start_date'), ('year', 'count', 'end_date'), ('month', 'order', 'end_date')]
    3146            ), m._get_unique_checks()
    3247        )
    3348
    class PerformUniqueChecksTest(unittest.TestCase):  
    4762        l = len(connection.queries)
    4863        mtv = ModelToValidate(number=10, name='Some Name')
    4964        setattr(mtv, '_adding', True)
    50         mtv.full_validate()
     65        mtv.full_clean()
    5166        self.assertEqual(l+1, len(connection.queries))
    5267
    5368    def test_primary_key_unique_check_not_performed_when_not_adding(self):
    5469        """Regression test for #12132"""
    5570        l = len(connection.queries)
    5671        mtv = ModelToValidate(number=10, name='Some Name')
    57         mtv.full_validate()
     72        mtv.full_clean()
    5873        self.assertEqual(l, len(connection.queries))
     74
  • tests/modeltests/validation/tests.py

    diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py
    index c00070b..b3af77c 100644
    a b  
    1 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
    2 from django.db import models
    3 
     1from django import forms
     2from django.test import TestCase
     3from django.core.exceptions import NON_FIELD_ERRORS
    44from modeltests.validation import ValidationTestCase
    5 from models import *
     5from modeltests.validation.models import Author, Article, ModelToValidate
    66
    7 from validators import TestModelsWithValidators
    8 from test_unique import GetUniqueCheckTests, PerformUniqueChecksTest
    9 from test_custom_messages import CustomMessagesTest
     7# Import other tests for this package.
     8from modeltests.validation.validators import TestModelsWithValidators
     9from modeltests.validation.test_unique import GetUniqueCheckTests, PerformUniqueChecksTest
     10from modeltests.validation.test_custom_messages import CustomMessagesTest
    1011
    1112
    1213class BaseModelValidationTests(ValidationTestCase):
    1314
    1415    def test_missing_required_field_raises_error(self):
    1516        mtv = ModelToValidate(f_with_custom_validator=42)
    16         self.assertFailsValidation(mtv.full_validate, ['name', 'number'])
     17        self.assertFailsValidation(mtv.full_clean, ['name', 'number'])
    1718
    1819    def test_with_correct_value_model_validates(self):
    1920        mtv = ModelToValidate(number=10, name='Some Name')
    20         self.assertEqual(None, mtv.full_validate())
     21        self.assertEqual(None, mtv.full_clean())
    2122
    22     def test_custom_validate_method_is_called(self):
     23    def test_custom_validate_method(self):
    2324        mtv = ModelToValidate(number=11)
    24         self.assertFailsValidation(mtv.full_validate, [NON_FIELD_ERRORS, 'name'])
     25        self.assertFailsValidation(mtv.full_clean, [NON_FIELD_ERRORS, 'name'])
    2526
    2627    def test_wrong_FK_value_raises_error(self):
    2728        mtv=ModelToValidate(number=10, name='Some Name', parent_id=3)
    28         self.assertFailsValidation(mtv.full_validate, ['parent'])
     29        self.assertFailsValidation(mtv.full_clean, ['parent'])
    2930
    3031    def test_correct_FK_value_validates(self):
    3132        parent = ModelToValidate.objects.create(number=10, name='Some Name')
    3233        mtv=ModelToValidate(number=10, name='Some Name', parent_id=parent.pk)
    33         self.assertEqual(None, mtv.full_validate())
     34        self.assertEqual(None, mtv.full_clean())
    3435
    3536    def test_wrong_email_value_raises_error(self):
    3637        mtv = ModelToValidate(number=10, name='Some Name', email='not-an-email')
    37         self.assertFailsValidation(mtv.full_validate, ['email'])
     38        self.assertFailsValidation(mtv.full_clean, ['email'])
    3839
    3940    def test_correct_email_value_passes(self):
    4041        mtv = ModelToValidate(number=10, name='Some Name', email='valid@email.com')
    41         self.assertEqual(None, mtv.full_validate())
     42        self.assertEqual(None, mtv.full_clean())
    4243
    4344    def test_wrong_url_value_raises_error(self):
    4445        mtv = ModelToValidate(number=10, name='Some Name', url='not a url')
    45         self.assertFieldFailsValidationWithMessage(mtv.full_validate, 'url', [u'Enter a valid value.'])
     46        self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'Enter a valid value.'])
    4647
    4748    def test_correct_url_but_nonexisting_gives_404(self):
    4849        mtv = ModelToValidate(number=10, name='Some Name', url='http://google.com/we-love-microsoft.html')
    49         self.assertFieldFailsValidationWithMessage(mtv.full_validate, 'url', [u'This URL appears to be a broken link.'])
     50        self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', [u'This URL appears to be a broken link.'])
    5051
    5152    def test_correct_url_value_passes(self):
    5253        mtv = ModelToValidate(number=10, name='Some Name', url='http://www.djangoproject.com/')
    53         self.assertEqual(None, mtv.full_validate()) # This will fail if there's no Internet connection
     54        self.assertEqual(None, mtv.full_clean()) # This will fail if there's no Internet connection
    5455
    5556    def test_text_greater_that_charfields_max_length_eaises_erros(self):
    5657        mtv = ModelToValidate(number=10, name='Some Name'*100)
    57         self.assertFailsValidation(mtv.full_validate, ['name',])
    58 
     58        self.assertFailsValidation(mtv.full_clean, ['name',])
     59
     60class ArticleForm(forms.ModelForm):
     61    class Meta:
     62        model = Article
     63        exclude = ['author']
     64
     65class ModelFormsTests(TestCase):
     66    def setUp(self):
     67        self.author = Author.objects.create(name='Joseph Kocherhans')
     68
     69    def test_partial_validation(self):
     70        # Make sure the "commit=False and set field values later" idiom still
     71        # works with model validation.
     72        data = {
     73            'title': 'The state of model validation',
     74            'pub_date': '2010-1-10 14:49:00'
     75        }
     76        form = ArticleForm(data)
     77        self.assertEqual(form.errors.keys(), [])
     78        article = form.save(commit=False)
     79        article.author = self.author
     80        article.save()
     81
     82    def test_full_validation(self):
     83        data = {
     84            'title': 'The state of model validation',
     85            'pub_date': '2010-1-10 14:49:00'
     86        }
     87        article = Article(author_id=self.author.id)
     88        form = ArticleForm(data, instance=article, validate_model=True)
     89        self.assertEqual(form.errors.keys(), [])
     90        article = form.save()
     91
     92    def test_full_validation_with_empty_blank_field(self):
     93        # Since a value for pub_date wasn't provided and the field is
     94        # blank=True, model-validation should pass.
     95        # Also, Article.clean() should be run since validate_model=True, so
     96        # pub_date will be filled after validation, so the form should save
     97        # cleanly even though pub_date is not allowed to be null.
     98        data = {
     99            'title': 'The state of model validation',
     100        }
     101        article = Article(author_id=self.author.id)
     102        form = ArticleForm(data, instance=article, validate_model=True)
     103        self.assertEqual(form.errors.keys(), [])
     104        self.assertNotEqual(form.instance.pub_date, None)
     105        article = form.save()
     106
     107    def test_full_validation_with_invalid_blank_field(self):
     108        # Even though pub_date is set to blank=True, an invalid value was
     109        # provided, so it should fail model validation.
     110        data = {
     111            'title': 'The state of model validation',
     112            'pub_date': 'never'
     113        }
     114        article = Article(author_id=self.author.id)
     115        form = ArticleForm(data, instance=article, validate_model=True)
     116        self.assertEqual(form.errors.keys(), ['pub_date'])
  • tests/modeltests/validation/validators.py

    diff --git a/tests/modeltests/validation/validators.py b/tests/modeltests/validation/validators.py
    index dc4cd4e..3ad2c40 100644
    a b from models import *  
    66class TestModelsWithValidators(ValidationTestCase):
    77    def test_custom_validator_passes_for_correct_value(self):
    88        mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42)
    9         self.assertEqual(None, mtv.full_validate())
     9        self.assertEqual(None, mtv.full_clean())
    1010
    1111    def test_custom_validator_raises_error_for_incorrect_value(self):
    1212        mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=12)
    13         self.assertFailsValidation(mtv.full_validate, ['f_with_custom_validator'])
     13        self.assertFailsValidation(mtv.full_clean, ['f_with_custom_validator'])
    1414        self.assertFieldFailsValidationWithMessage(
    15             mtv.full_validate,
     15            mtv.full_clean,
    1616            'f_with_custom_validator',
    1717            [u'This is not the answer to life, universe and everything!']
    1818        )
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index 899fff8..351974b 100644
    a b import re  
    44import datetime
    55from django.core.files import temp as tempfile
    66from django.test import TestCase
    7 from django.contrib.auth.models import User, Permission
     7from django.contrib.auth import admin # Register auth models with the admin.
     8from django.contrib.auth.models import User, Permission, UNUSABLE_PASSWORD
    89from django.contrib.contenttypes.models import ContentType
    910from django.contrib.admin.models import LogEntry, DELETION
    1011from django.contrib.admin.sites import LOGIN_FORM_KEY
    class ReadonlyTest(TestCase):  
    17531754        self.assertEqual(Post.objects.count(), 2)
    17541755        p = Post.objects.order_by('-id')[0]
    17551756        self.assertEqual(p.posted, datetime.date.today())
     1757
     1758class IncompleteFormTest(TestCase):
     1759    """
     1760    Tests validation of a ModelForm that doesn't explicitly have all data
     1761    corresponding to model fields. Model validation shouldn't fail
     1762    such a forms.
     1763    """
     1764    fixtures = ['admin-views-users.xml']
     1765
     1766    def setUp(self):
     1767       self.client.login(username='super', password='secret')
     1768
     1769    def tearDown(self):
     1770       self.client.logout()
     1771
     1772    def test_user_creation(self):
     1773       response = self.client.post('/test_admin/admin/auth/user/add/', {
     1774           'username': 'newuser',
     1775           'password1': 'newpassword',
     1776           'password2': 'newpassword',
     1777       })
     1778       new_user = User.objects.order_by('-id')[0]
     1779       self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
     1780       self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD)
     1781
     1782    def test_password_mismatch(self):
     1783       response = self.client.post('/test_admin/admin/auth/user/add/', {
     1784           'username': 'newuser',
     1785           'password1': 'newpassword',
     1786           'password2': 'mismatch',
     1787       })
     1788       self.assertEquals(response.status_code, 200)
     1789       self.assert_('password' not in response.context['form'].errors)
     1790       self.assertFormError(response, 'form', 'password2', ["The two password fields didn't match."])
  • tests/regressiontests/inline_formsets/tests.py

    diff --git a/tests/regressiontests/inline_formsets/tests.py b/tests/regressiontests/inline_formsets/tests.py
    index be313f3..aef6b3f 100644
    a b class DeletionTests(TestCase):  
    8181        regression for #10750
    8282        """
    8383        # exclude some required field from the forms
    84         ChildFormSet = inlineformset_factory(School, Child)
     84        ChildFormSet = inlineformset_factory(School, Child, exclude=['father', 'mother'])
    8585        school = School.objects.create(name=u'test')
    8686        mother = Parent.objects.create(name=u'mother')
    8787        father = Parent.objects.create(name=u'father')
    class DeletionTests(TestCase):  
    8989            'child_set-TOTAL_FORMS': u'1',
    9090            'child_set-INITIAL_FORMS': u'0',
    9191            'child_set-0-name': u'child',
    92             'child_set-0-mother': unicode(mother.pk),
    93             'child_set-0-father': unicode(father.pk),
    9492        }
    9593        formset = ChildFormSet(data, instance=school)
    9694        self.assertEqual(formset.is_valid(), True)
    9795        objects = formset.save(commit=False)
    98         self.assertEqual(school.child_set.count(), 0)
    99         objects[0].save()
     96        for obj in objects:
     97            obj.mother = mother
     98            obj.father = father
     99            obj.save()
    100100        self.assertEqual(school.child_set.count(), 1)
    101101
Back to Top