Commit 38ba3775 authored by Carl Meyer's avatar Carl Meyer
Browse files

Fixed #14234 -- Re-validating a model instance added via ModelForm no longer...

Fixed #14234 -- Re-validating a model instance added via ModelForm no longer throws spurious PK uniqueness errors. Thanks to David Reynolds and Jeremy Dunck.

Also moved Model._adding to Model._state.adding to reduce instance namespace footprint.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14612 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 9cebb526
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -262,6 +262,10 @@ class ModelState(object):
    """
    def __init__(self, db=None):
        self.db = db
        # If true, uniqueness validation checks will consider this a new, as-yet-unsaved object.
        # Necessary for correct validation of new instances of objects with explicit (non-auto) PKs.
        # This impacts validation only; it has no effect on the actual save.
        self.adding = False

class Model(object):
    __metaclass__ = ModelBase
@@ -555,12 +559,15 @@ class Model(object):

        # Store the database on which the object was saved
        self._state.db = using
        # Once saved, this is no longer a to-be-added instance.
        self._state.adding = False

        # Signal that the save is complete
        if origin and not meta.auto_created:
            signals.post_save.send(sender=origin, instance=self,
                created=(not record_exists), raw=raw, using=using)


    save_base.alters_data = True

    def delete(self, using=None):
@@ -701,7 +708,7 @@ class Model(object):
                if lookup_value is None:
                    # no value, skip the lookup
                    continue
                if f.primary_key and not getattr(self, '_adding', False):
                if f.primary_key and not self._state.adding:
                    # no need to check for unique primary key when editing
                    continue
                lookup_kwargs[str(field_name)] = lookup_value
@@ -714,7 +721,7 @@ class Model(object):

            # Exclude the current object from the query if we are editing an
            # instance (as opposed to creating a new one)
            if not getattr(self, '_adding', False) and self.pk is not None:
            if not self._state.adding and self.pk is not None:
                qs = qs.exclude(pk=self.pk)

            if qs.exists():
@@ -744,7 +751,7 @@ class Model(object):
            qs = model_class._default_manager.filter(**lookup_kwargs)
            # Exclude the current object from the query if we are editing an
            # instance (as opposed to creating a new one)
            if not getattr(self, '_adding', False) and self.pk is not None:
            if not self._state.adding and self.pk is not None:
                qs = qs.exclude(pk=self.pk)

            if qs.exists():
+2 −2
Original line number Diff line number Diff line
@@ -254,10 +254,10 @@ class BaseModelForm(BaseForm):
            # if we didn't get an instance, instantiate a new one
            self.instance = opts.model()
            object_data = {}
            self.instance._adding = True
            self.instance._state.adding = True
        else:
            self.instance = instance
            self.instance._adding = False
            self.instance._state.adding = False
            object_data = model_to_dict(instance, opts.fields, opts.exclude)
        # if initial was provided, it should override the values from instance
        if initial is not None:
+13 −0
Original line number Diff line number Diff line
@@ -5,28 +5,34 @@ import tempfile
from django.db import models
from django.core.files.storage import FileSystemStorage


temp_storage_location = tempfile.mkdtemp()
temp_storage = FileSystemStorage(location=temp_storage_location)


class BoundaryModel(models.Model):
    positive_integer = models.PositiveIntegerField(null=True, blank=True)


callable_default_value = 0
def callable_default():
    global callable_default_value
    callable_default_value = callable_default_value + 1
    return callable_default_value


class Defaults(models.Model):
    name = models.CharField(max_length=255, default='class default value')
    def_date = models.DateField(default = datetime.date(1980, 1, 1))
    value = models.IntegerField(default=42)
    callable_default = models.IntegerField(default=callable_default)


class ChoiceModel(models.Model):
    """For ModelChoiceField and ModelMultipleChoiceField tests."""
    name = models.CharField(max_length=10)


class ChoiceOptionModel(models.Model):
    """Destination for ChoiceFieldModel's ForeignKey.
    Can't reuse ChoiceModel because error_message tests require that it have no instances."""
@@ -38,6 +44,7 @@ class ChoiceOptionModel(models.Model):
    def __unicode__(self):
        return u'ChoiceOption %d' % self.pk


class ChoiceFieldModel(models.Model):
    """Model with ForeignKey to another model, for testing ModelForm
    generation with ModelChoiceField."""
@@ -51,11 +58,17 @@ class ChoiceFieldModel(models.Model):
    multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice_int',
                                              default=lambda: [1])


class FileModel(models.Model):
    file = models.FileField(storage=temp_storage, upload_to='tests')


class Group(models.Model):
    name = models.CharField(max_length=10)

    def __unicode__(self):
        return u'%s' % self.name


class Cheese(models.Model):
   name = models.CharField(max_length=100)
+21 −0
Original line number Diff line number Diff line
@@ -3,6 +3,9 @@ from django.forms import *
from django.utils.unittest import TestCase
from django.utils.translation import ugettext_lazy, activate, deactivate

from regressiontests.forms.models import Cheese


class FormsRegressionsTestCase(TestCase):
    def test_class(self):
        # Tests to prevent against recurrences of earlier bugs.
@@ -120,3 +123,21 @@ class FormsRegressionsTestCase(TestCase):

        f = SomeForm({'field': ['<script>']})
        self.assertEqual(t.render(Context({'form': f})), u'<ul class="errorlist"><li>field<ul class="errorlist"><li>&quot;&lt;script&gt;&quot; is not a valid value for a primary key.</li></ul></li></ul>')

    def test_regression_14234(self):
        """
        Re-cleaning an instance that was added via a ModelForm should not raise
        a pk uniqueness error.

        """
        class CheeseForm(ModelForm):
            class Meta:
                model = Cheese

        form = CheeseForm({
            'name': 'Brie',
        })
        if form.is_valid():
            obj = form.save()
            obj.name = 'Camembert'
            obj.full_clean()