mirror of https://github.com/django/django.git
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.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
|
||||||
from django.db.models.query import delete_objects, Q, CollectedObjects
|
from django.db.models.query import delete_objects, Q, CollectedObjects
|
||||||
from django.db.models.options import Options
|
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 import signals
|
||||||
from django.db.models.loading import register_models, get_model
|
from django.db.models.loading import register_models, get_model
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
|
@ -268,22 +268,31 @@ class Model(object):
|
||||||
|
|
||||||
pk = property(_get_pk_val, _set_pk_val)
|
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
|
Saves the current instance. Override this in a subclass if you want to
|
||||||
control the saving process.
|
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
|
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
|
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
|
override this method. It's separate from save() in order to hide the
|
||||||
need for overrides of save() to pass around internal-only parameters
|
need for overrides of save() to pass around internal-only parameters
|
||||||
('raw' and 'cls').
|
('raw' and 'cls').
|
||||||
"""
|
"""
|
||||||
|
assert not (force_insert and force_update)
|
||||||
if not cls:
|
if not cls:
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
meta = self._meta
|
meta = self._meta
|
||||||
|
@ -319,15 +328,20 @@ class Model(object):
|
||||||
manager = cls._default_manager
|
manager = cls._default_manager
|
||||||
if pk_set:
|
if pk_set:
|
||||||
# Determine whether a record with the primary key already exists.
|
# 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.
|
# 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]
|
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:
|
else:
|
||||||
record_exists = False
|
record_exists = False
|
||||||
if not pk_set or not record_exists:
|
if not pk_set or not record_exists:
|
||||||
if not pk_set:
|
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)]
|
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:
|
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]
|
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."
|
"Cannot update a query once a slice has been taken."
|
||||||
query = self.query.clone(sql.UpdateQuery)
|
query = self.query.clone(sql.UpdateQuery)
|
||||||
query.add_update_values(kwargs)
|
query.add_update_values(kwargs)
|
||||||
query.execute_sql(None)
|
rows = query.execute_sql(None)
|
||||||
transaction.commit_unless_managed()
|
transaction.commit_unless_managed()
|
||||||
self._result_cache = None
|
self._result_cache = None
|
||||||
|
return rows
|
||||||
update.alters_data = True
|
update.alters_data = True
|
||||||
|
|
||||||
def _update(self, values):
|
def _update(self, values):
|
||||||
|
@ -415,8 +416,8 @@ class QuerySet(object):
|
||||||
"Cannot update a query once a slice has been taken."
|
"Cannot update a query once a slice has been taken."
|
||||||
query = self.query.clone(sql.UpdateQuery)
|
query = self.query.clone(sql.UpdateQuery)
|
||||||
query.add_update_fields(values)
|
query.add_update_fields(values)
|
||||||
query.execute_sql(None)
|
|
||||||
self._result_cache = None
|
self._result_cache = None
|
||||||
|
return query.execute_sql(None)
|
||||||
_update.alters_data = True
|
_update.alters_data = True
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
|
|
|
@ -109,9 +109,17 @@ class UpdateQuery(Query):
|
||||||
related_updates=self.related_updates.copy, **kwargs)
|
related_updates=self.related_updates.copy, **kwargs)
|
||||||
|
|
||||||
def execute_sql(self, result_type=None):
|
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():
|
for query in self.get_related_updates():
|
||||||
query.execute_sql(result_type)
|
query.execute_sql(result_type)
|
||||||
|
return rows
|
||||||
|
|
||||||
def as_sql(self):
|
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
|
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
|
value explicitly when saving new objects, if you cannot guarantee the
|
||||||
primary-key value is unused. For more on this nuance, see
|
primary-key value is unused. For more on this nuance, see `Explicitly
|
||||||
"Explicitly specifying auto-primary-key values" above.
|
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
|
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