diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 3c7dcfa02e..50c87ff276 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -162,12 +162,12 @@ class DeserializedObject(object): return "" % smart_str(self.object) def save(self, save_m2m=True): - # Call save on the Model baseclass directly. This bypasses any + # Call save on the Model baseclass directly. This bypasses any # model-defined save. The save is also forced to be raw. - # This ensures that the data that is deserialized is literally + # This ensures that the data that is deserialized is literally # what came from the file, not post-processed by pre_save/save # methods. - models.Model.save(self.object, raw=True) + models.Model.save_base(self.object, raw=True) if self.m2m_data and save_m2m: for accessor_name, object_list in self.m2m_data.items(): setattr(self.object, accessor_name, object_list) diff --git a/django/db/models/base.py b/django/db/models/base.py index 99c6cea862..6c2bd858d4 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -253,19 +253,34 @@ class Model(object): raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0] dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self) - def save(self, raw=False, cls=None): + def save(self): + """ + Save the current instance. Override this in a subclass if you want to + control the saving process. + """ + self.save_base() + + save.alters_data = True + + def save_base(self, raw=False, cls=None): + """ + Does the heavy-lifting involved in saving. Subclasses shouldn't need to + override this method. It's separate from save() in order to hide the + need for overrides of save() to pass around internal-only parameters + ('raw' and 'cls'). + """ if not cls: - dispatcher.send(signal=signals.pre_save, sender=self.__class__, - instance=self, raw=raw) cls = self.__class__ meta = self._meta signal = True + dispatcher.send(signal=signals.pre_save, sender=self.__class__, + instance=self, raw=raw) else: meta = cls._meta signal = False for parent, field in meta.parents.items(): - self.save(raw, parent) + self.save_base(raw, parent) setattr(self, field.attname, self._get_pk_val(parent._meta)) non_pks = [f for f in meta.local_fields if not f.primary_key] @@ -311,12 +326,9 @@ class Model(object): transaction.commit_unless_managed() if signal: - # Run any post-save hooks. dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self, created=(not record_exists), raw=raw) - save.alters_data = True - def validate(self): """ First coerces all fields on this instance to their proper Python types. diff --git a/docs/db-api.txt b/docs/db-api.txt index 3cab6574ea..e626d1da86 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -160,37 +160,6 @@ When you save an object, Django performs the following steps: is used to provide notification that an object has been successfully saved. (These signals are not yet documented.) -Raw saves -~~~~~~~~~ - -**New in Django development version** - -The pre-processing step (#2 in the previous section) is useful, but it modifies -the data stored in a field. This can cause problems if you're relying upon the -data you provide being used as-is. - -For example, if you're setting up conditions for a test, you'll want the test -conditions to be repeatable. If pre-processing is performed, the data used -to specify test conditions may be modified, changing the conditions for the -test each time the test is run. - -In cases such as this, you need to prevent pre-processing from being performed -when you save an object. To do this, you can invoke a **raw save** by passing -``raw=True`` as an argument to the ``save()`` method:: - - b4.save(raw=True) # Save object, but do no pre-processing - -A raw save skips the usual data pre-processing that is performed during the -save. All other steps in the save (pre-save signal, data preparation, data -insertion, and post-save signal) are performed as normal. - -.. admonition:: When to use a raw save - - Generally speaking, you shouldn't need to use a raw save. Disabling field - pre-processing is an extraordinary measure that should only be required - in extraordinary circumstances, such as setting up reliable test - conditions. - Saving changes to objects ========================= diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py index 5b0f5f2285..fc58d90a14 100644 --- a/tests/modeltests/signals/models.py +++ b/tests/modeltests/signals/models.py @@ -66,7 +66,8 @@ post_save_nokwargs signal post_save signal, Tom Smith Is updated ->>> p1.save(raw=True) +# Calling an internal method purely so that we can trigger a "raw" save. +>>> p1.save_base(raw=True) pre_save_nokwargs signal pre_save signal, Tom Smith Is raw diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index 24111308d7..9c42e85122 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -31,13 +31,13 @@ except ImportError: def data_create(pk, klass, data): instance = klass(id=pk) instance.data = data - models.Model.save(instance, raw=True) + models.Model.save_base(instance, raw=True) return instance def generic_create(pk, klass, data): instance = klass(id=pk) instance.data = data[0] - models.Model.save(instance, raw=True) + models.Model.save_base(instance, raw=True) for tag in data[1:]: instance.tags.create(data=tag) return instance @@ -45,25 +45,25 @@ def generic_create(pk, klass, data): def fk_create(pk, klass, data): instance = klass(id=pk) setattr(instance, 'data_id', data) - models.Model.save(instance, raw=True) + models.Model.save_base(instance, raw=True) return instance def m2m_create(pk, klass, data): instance = klass(id=pk) - models.Model.save(instance, raw=True) + models.Model.save_base(instance, raw=True) instance.data = data return instance def o2o_create(pk, klass, data): instance = klass() instance.data_id = data - models.Model.save(instance, raw=True) + models.Model.save_base(instance, raw=True) return instance def pk_create(pk, klass, data): instance = klass() instance.data = data - models.Model.save(instance, raw=True) + models.Model.save_base(instance, raw=True) return instance # A set of functions that can be used to compare @@ -309,7 +309,7 @@ def fieldsTest(format, self): management.call_command('flush', verbosity=0, interactive=False) obj = ComplexModel(field1='first',field2='second',field3='third') - obj.save(raw=True) + obj.save() # Serialize then deserialize the test database serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1','field3')) @@ -325,7 +325,7 @@ def streamTest(format, self): management.call_command('flush', verbosity=0, interactive=False) obj = ComplexModel(field1='first',field2='second',field3='third') - obj.save(raw=True) + obj.save() # Serialize the test database to a stream stream = StringIO()