mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	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:
		| @@ -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
									
								
								tests/modeltests/force_insert_update/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/modeltests/force_insert_update/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										62
									
								
								tests/modeltests/force_insert_update/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								tests/modeltests/force_insert_update/models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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: ... | ||||
|  | ||||
| """ | ||||
| } | ||||
		Reference in New Issue
	
	Block a user