Added the ability to force an SQL insert (or force an update) via a model's
save() method. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8267 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
f53e4d80b4
commit
dc14b29fb3
|
@ -17,7 +17,7 @@ from django.db.models.fields import AutoField
|
|||
from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
|
||||
from django.db.models.query import delete_objects, Q, CollectedObjects
|
||||
from django.db.models.options import Options
|
||||
from django.db import connection, transaction
|
||||
from django.db import connection, transaction, DatabaseError
|
||||
from django.db.models import signals
|
||||
from django.db.models.loading import register_models, get_model
|
||||
from django.utils.functional import curry
|
||||
|
@ -268,22 +268,31 @@ class Model(object):
|
|||
|
||||
pk = property(_get_pk_val, _set_pk_val)
|
||||
|
||||
def save(self):
|
||||
def save(self, force_insert=False, force_update=False):
|
||||
"""
|
||||
Saves the current instance. Override this in a subclass if you want to
|
||||
control the saving process.
|
||||
|
||||
The 'force_insert' and 'force_update' parameters can be used to insist
|
||||
that the "save" must be an SQL insert or update (or equivalent for
|
||||
non-SQL backends), respectively. Normally, they should not be set.
|
||||
"""
|
||||
self.save_base()
|
||||
if force_insert and force_update:
|
||||
raise ValueError("Cannot force both insert and updating in "
|
||||
"model saving.")
|
||||
self.save_base(force_insert=force_insert, force_update=force_update)
|
||||
|
||||
save.alters_data = True
|
||||
|
||||
def save_base(self, raw=False, cls=None):
|
||||
def save_base(self, raw=False, cls=None, force_insert=False,
|
||||
force_update=False):
|
||||
"""
|
||||
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').
|
||||
"""
|
||||
assert not (force_insert and force_update)
|
||||
if not cls:
|
||||
cls = self.__class__
|
||||
meta = self._meta
|
||||
|
@ -319,15 +328,20 @@ class Model(object):
|
|||
manager = cls._default_manager
|
||||
if pk_set:
|
||||
# Determine whether a record with the primary key already exists.
|
||||
if manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by():
|
||||
if (force_update or (not force_insert and
|
||||
manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
|
||||
# It does already exist, so do an UPDATE.
|
||||
if non_pks:
|
||||
if force_update or non_pks:
|
||||
values = [(f, None, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
|
||||
manager.filter(pk=pk_val)._update(values)
|
||||
rows = manager.filter(pk=pk_val)._update(values)
|
||||
if force_update and not rows:
|
||||
raise DatabaseError("Forced update did not affect any rows.")
|
||||
else:
|
||||
record_exists = False
|
||||
if not pk_set or not record_exists:
|
||||
if not pk_set:
|
||||
if force_update:
|
||||
raise ValueError("Cannot force an update in save() with no primary key.")
|
||||
values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)]
|
||||
else:
|
||||
values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields]
|
||||
|
|
|
@ -399,9 +399,10 @@ class QuerySet(object):
|
|||
"Cannot update a query once a slice has been taken."
|
||||
query = self.query.clone(sql.UpdateQuery)
|
||||
query.add_update_values(kwargs)
|
||||
query.execute_sql(None)
|
||||
rows = query.execute_sql(None)
|
||||
transaction.commit_unless_managed()
|
||||
self._result_cache = None
|
||||
return rows
|
||||
update.alters_data = True
|
||||
|
||||
def _update(self, values):
|
||||
|
@ -415,8 +416,8 @@ class QuerySet(object):
|
|||
"Cannot update a query once a slice has been taken."
|
||||
query = self.query.clone(sql.UpdateQuery)
|
||||
query.add_update_fields(values)
|
||||
query.execute_sql(None)
|
||||
self._result_cache = None
|
||||
return query.execute_sql(None)
|
||||
_update.alters_data = True
|
||||
|
||||
##################################################
|
||||
|
|
|
@ -109,9 +109,17 @@ class UpdateQuery(Query):
|
|||
related_updates=self.related_updates.copy, **kwargs)
|
||||
|
||||
def execute_sql(self, result_type=None):
|
||||
super(UpdateQuery, self).execute_sql(result_type)
|
||||
"""
|
||||
Execute the specified update. Returns the number of rows affected by
|
||||
the primary update query (there could be other updates on related
|
||||
tables, but their rowcounts are not returned).
|
||||
"""
|
||||
cursor = super(UpdateQuery, self).execute_sql(result_type)
|
||||
rows = cursor.rowcount
|
||||
del cursor
|
||||
for query in self.get_related_updates():
|
||||
query.execute_sql(result_type)
|
||||
return rows
|
||||
|
||||
def as_sql(self):
|
||||
"""
|
||||
|
|
|
@ -213,8 +213,26 @@ follows this algorithm:
|
|||
|
||||
The one gotcha here is that you should be careful not to specify a primary-key
|
||||
value explicitly when saving new objects, if you cannot guarantee the
|
||||
primary-key value is unused. For more on this nuance, see
|
||||
"Explicitly specifying auto-primary-key values" above.
|
||||
primary-key value is unused. For more on this nuance, see `Explicitly
|
||||
specifying auto-primary-key values`_ above and `Forcing an INSERT or UPDATE`_
|
||||
below.
|
||||
|
||||
Forcing an INSERT or UPDATE
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**New in Django development version**
|
||||
|
||||
In some rare circumstances, it's necesary to be able to force the ``save()``
|
||||
method to perform an SQL ``INSERT`` and not fall back to doing an ``UPDATE``.
|
||||
Or vice-versa: update, if possible, but not insert a new row. In these cases
|
||||
you can pass the ``force_insert=True`` or ``force_update=True`` parameters to
|
||||
the ``save()`` method. Passing both parameters is an error, since you cannot
|
||||
both insert *and* update at the same time.
|
||||
|
||||
It should be very rare that you'll need to use these parameters. Django will
|
||||
almost always do the right thing and trying to override that will lead to
|
||||
errors that are difficult to track down. This feature is for advanced use
|
||||
only.
|
||||
|
||||
Retrieving objects
|
||||
==================
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
"""
|
||||
Tests for forcing insert and update queries (instead of Django's normal
|
||||
automatic behaviour).
|
||||
"""
|
||||
from django.db import models
|
||||
|
||||
class Counter(models.Model):
|
||||
name = models.CharField(max_length = 10)
|
||||
value = models.IntegerField()
|
||||
|
||||
class WithCustomPK(models.Model):
|
||||
name = models.IntegerField(primary_key=True)
|
||||
value = models.IntegerField()
|
||||
|
||||
__test__ = {"API_TESTS": """
|
||||
>>> c = Counter.objects.create(name="one", value=1)
|
||||
|
||||
# The normal case
|
||||
>>> c.value = 2
|
||||
>>> c.save()
|
||||
|
||||
# Same thing, via an update
|
||||
>>> c.value = 3
|
||||
>>> c.save(force_update=True)
|
||||
|
||||
# Won't work because force_update and force_insert are mutually exclusive
|
||||
>>> c.value = 4
|
||||
>>> c.save(force_insert=True, force_update=True)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Cannot force both insert and updating in model saving.
|
||||
|
||||
# Try to update something that doesn't have a primary key in the first place.
|
||||
>>> c1 = Counter(name="two", value=2)
|
||||
>>> c1.save(force_update=True)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Cannot force an update in save() with no primary key.
|
||||
|
||||
>>> c1.save(force_insert=True)
|
||||
|
||||
# Won't work because we can't insert a pk of the same value.
|
||||
>>> c.value = 5
|
||||
>>> c.save(force_insert=True)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IntegrityError: ...
|
||||
|
||||
# Work around transaction failure cleaning up for PostgreSQL.
|
||||
>>> from django.db import connection
|
||||
>>> connection.close()
|
||||
|
||||
# Trying to update should still fail, even with manual primary keys, if the
|
||||
# data isn't in the database already.
|
||||
>>> obj = WithCustomPK(name=1, value=1)
|
||||
>>> obj.save(force_update=True)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DatabaseError: ...
|
||||
|
||||
"""
|
||||
}
|
Loading…
Reference in New Issue