mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +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.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
									
								
								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