Skip to content

Commit 2d6d20d

Browse files
committed
Fixed #4459 -- Added 'raw' argument to save method, to override any pre-save processing, and modified serializers to use a raw-save. This enables serialization of DateFields with auto_now/auto_now_add. Also modified serializers to invoke save() directly on the model baseclass, to avoid any (potentially order-dependent, data modifying) behavior in a custom save() method.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5658 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent 090aa52 commit 2d6d20d

File tree

5 files changed

+55
-14
lines changed

5 files changed

+55
-14
lines changed

django/core/serializers/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,12 @@ def __repr__(self):
158158
return "<DeserializedObject: %s>" % smart_str(self.object)
159159

160160
def save(self, save_m2m=True):
161-
self.object.save()
161+
# Call save on the Model baseclass directly. This bypasses any
162+
# model-defined save. The save is also forced to be raw.
163+
# This ensures that the data that is deserialized is literally
164+
# what came from the file, not post-processed by pre_save/save
165+
# methods.
166+
models.Model.save(self.object, raw=True)
162167
if self.m2m_data and save_m2m:
163168
for accessor_name, object_list in self.m2m_data.items():
164169
setattr(self.object, accessor_name, object_list)

django/db/models/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def _prepare(cls):
201201

202202
_prepare = classmethod(_prepare)
203203

204-
def save(self):
204+
def save(self, raw=False):
205205
dispatcher.send(signal=signals.pre_save, sender=self.__class__, instance=self)
206206

207207
non_pks = [f for f in self._meta.fields if not f.primary_key]
@@ -218,7 +218,7 @@ def save(self):
218218
self._meta.pk.get_db_prep_lookup('exact', pk_val))
219219
# If it does already exist, do an UPDATE.
220220
if cursor.fetchone()[0] > 0:
221-
db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks]
221+
db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False)) for f in non_pks]
222222
if db_values:
223223
cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
224224
(backend.quote_name(self._meta.db_table),
@@ -229,11 +229,11 @@ def save(self):
229229
record_exists = False
230230
if not pk_set or not record_exists:
231231
field_names = [backend.quote_name(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
232-
db_values = [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
232+
db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
233233
# If the PK has been manually set, respect that.
234234
if pk_set:
235235
field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
236-
db_values += [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
236+
db_values += [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
237237
placeholders = ['%s'] * len(field_names)
238238
if self._meta.order_with_respect_to:
239239
field_names.append(backend.quote_name('_order'))

docs/db-api.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ happens.
118118
Explicitly specifying auto-primary-key values is mostly useful for bulk-saving
119119
objects, when you're confident you won't have primary-key collision.
120120

121+
Raw saves
122+
---------
123+
124+
When you save an Django object, some pre-processing will occur on the the data
125+
that is in the object. For example, if your model has a ``DateField`` with
126+
``auto_now=True`` set, the pre-save phase will alter the data in the object
127+
to ensure that the date field contains the current date stamp.
128+
129+
Although these automated changes can be very useful, there will be times when
130+
you just want to save the data as-is. In these cases, you can invoke a *Raw Save*
131+
by passing ``raw=True`` as an argument to the ``save()`` method::
132+
133+
b4.save(raw=True) # Saves object, but does no pre-processing
134+
135+
A raw save saves all the data in your object, but performs no pre-save processing
136+
on the data in the object.
137+
121138
Saving changes to objects
122139
=========================
123140

tests/regressiontests/serializers_regress/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,16 @@ class ComplexModel(models.Model):
209209
field1 = models.CharField(maxlength=10)
210210
field2 = models.CharField(maxlength=10)
211211
field3 = models.CharField(maxlength=10)
212+
213+
# Tests for handling fields with pre_save functions, or
214+
# models with save functions that modify data
215+
class AutoNowDateTimeData(models.Model):
216+
data = models.DateTimeField(null=True, auto_now=True)
217+
218+
class ModifyingSaveData(models.Model):
219+
data = models.IntegerField(null=True)
220+
221+
def save(self):
222+
"A save method that modifies the data in the object"
223+
self.data = 666
224+
super(ModifyingSaveData, self).save(raw)

tests/regressiontests/serializers_regress/tests.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,43 +24,46 @@
2424
from django.utils import _decimal as decimal
2525

2626
# A set of functions that can be used to recreate
27-
# test data objects of various kinds
27+
# test data objects of various kinds.
28+
# The save method is a raw base model save, to make
29+
# sure that the data in the database matches the
30+
# exact test case.
2831
def data_create(pk, klass, data):
2932
instance = klass(id=pk)
3033
instance.data = data
31-
instance.save()
34+
models.Model.save(instance, raw=True)
3235
return instance
3336

3437
def generic_create(pk, klass, data):
3538
instance = klass(id=pk)
3639
instance.data = data[0]
37-
instance.save()
40+
models.Model.save(instance, raw=True)
3841
for tag in data[1:]:
3942
instance.tags.create(data=tag)
4043
return instance
4144

4245
def fk_create(pk, klass, data):
4346
instance = klass(id=pk)
4447
setattr(instance, 'data_id', data)
45-
instance.save()
48+
models.Model.save(instance, raw=True)
4649
return instance
4750

4851
def m2m_create(pk, klass, data):
4952
instance = klass(id=pk)
50-
instance.save()
53+
models.Model.save(instance, raw=True)
5154
instance.data = data
5255
return instance
5356

5457
def o2o_create(pk, klass, data):
5558
instance = klass()
5659
instance.data_id = data
57-
instance.save()
60+
models.Model.save(instance, raw=True)
5861
return instance
5962

6063
def pk_create(pk, klass, data):
6164
instance = klass()
6265
instance.data = data
63-
instance.save()
66+
models.Model.save(instance, raw=True)
6467
return instance
6568

6669
# A set of functions that can be used to compare
@@ -249,6 +252,9 @@ def pk_compare(testcase, pk, klass, data):
249252
# (pk_obj, 770, TimePKData, datetime.time(10,42,37)),
250253
(pk_obj, 780, USStatePKData, "MA"),
251254
# (pk_obj, 790, XMLPKData, "<foo></foo>"),
255+
256+
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
257+
(data_obj, 810, ModifyingSaveData, 42),
252258
]
253259

254260
# Because Oracle treats the empty string as NULL, Oracle is expected to fail
@@ -303,7 +309,7 @@ def fieldsTest(format, self):
303309
management.flush(verbosity=0, interactive=False)
304310

305311
obj = ComplexModel(field1='first',field2='second',field3='third')
306-
obj.save()
312+
obj.save(raw=True)
307313

308314
# Serialize then deserialize the test database
309315
serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1','field3'))
@@ -319,7 +325,7 @@ def streamTest(format, self):
319325
management.flush(verbosity=0, interactive=False)
320326

321327
obj = ComplexModel(field1='first',field2='second',field3='third')
322-
obj.save()
328+
obj.save(raw=True)
323329

324330
# Serialize the test database to a stream
325331
stream = StringIO()

0 commit comments

Comments
 (0)