summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/query.py11
-rw-r--r--django/db/models/sql/query.py49
-rw-r--r--docs/db-api.txt28
-rw-r--r--tests/modeltests/update/__init__.py0
-rw-r--r--tests/modeltests/update/models.py60
5 files changed, 134 insertions, 14 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index cd153a49dc..136b6efbc3 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -255,6 +255,17 @@ class _QuerySet(object):
self._result_cache = None
delete.alters_data = True
+ def update(self, **kwargs):
+ """
+ Updates all elements in the current QuerySet, setting all the given
+ fields to the appropriate values.
+ """
+ query = self.query.clone(sql.UpdateQuery)
+ query.add_update_values(kwargs)
+ query.execute_sql(None)
+ self._result_cache=None
+ update.alters_Data = True
+
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index ee76523c3b..2f50fdfc98 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -174,6 +174,8 @@ class Query(object):
obj.extra_params = self.extra_params[:]
obj.extra_order_by = self.extra_order_by[:]
obj.__dict__.update(kwargs)
+ if hasattr(obj, '_setup_query'):
+ obj._setup_query()
return obj
def results_iter(self):
@@ -1159,6 +1161,12 @@ class UpdateQuery(Query):
"""
def __init__(self, *args, **kwargs):
super(UpdateQuery, self).__init__(*args, **kwargs)
+ self._setup_query()
+
+ def _setup_query(self):
+ """
+ Run on initialisation and after cloning.
+ """
self.values = []
def as_sql(self):
@@ -1166,22 +1174,24 @@ class UpdateQuery(Query):
Creates the SQL for this query. Returns the SQL string and list of
parameters.
"""
- assert len(self.tables) == 1, \
- "Can only update one table at a time."
+ self.select_related = False
+ self.pre_sql_setup()
+ if len(tables) != 1:
+ raise TypeError('Updates can only access a single database table at a time.')
result = ['UPDATE %s' % self.tables[0]]
result.append('SET')
qn = self.quote_name_unless_alias
- values = ['%s = %s' % (qn(v[0]), v[1]) for v in self.values]
+ values, update_params = [], []
+ for name, val in self.values:
+ if val is not None:
+ values.append('%s = %%s' % qn(name))
+ update_params.append(val)
+ else:
+ values.append('%s = NULL' % qn(name))
result.append(', '.join(values))
where, params = self.where.as_sql()
result.append('WHERE %s' % where)
- return ' '.join(result), tuple(params)
-
- def do_query(self, table, values, where):
- self.tables = [table]
- self.values = values
- self.where = where
- self.execute_sql(NONE)
+ return ' '.join(result), tuple(update_params + params)
def clear_related(self, related_field, pk_list):
"""
@@ -1191,13 +1201,24 @@ class UpdateQuery(Query):
This is used by the QuerySet.delete_objects() method.
"""
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
- where = self.where_class()
+ self.where = self.where_class()
f = self.model._meta.pk
- where.add((None, f.column, f, 'in',
+ self.where.add((None, f.column, f, 'in',
pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
AND)
- values = [(related_field.column, 'NULL')]
- self.do_query(self.model._meta.db_table, values, where)
+ self.values = [(related_field.column, None)]
+ self.execute_sql(None)
+
+ def add_update_values(self, values):
+ from django.db.models.base import Model
+ for name, val in values.items():
+ field, direct, m2m = self.model._meta.get_field_by_name(name)
+ if not direct or m2m:
+ # Can only update non-relation fields and foreign keys.
+ raise TypeError('Cannot update model field %r (only non-relations and foreign keys permitted).' % field)
+ if field.rel and isinstance(val, Model):
+ val = val.pk
+ self.values.append((field.column, val))
class DateQuery(Query):
"""
diff --git a/docs/db-api.txt b/docs/db-api.txt
index 342f635219..c37ebd083a 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -1972,6 +1972,34 @@ complete query set::
Entry.objects.all().delete()
+Updating multiple objects at once
+=================================
+
+**New in Django development version**
+
+Sometimes you want to set a field to a particular value for all the objects in
+a queryset. You can do this with the ``update()`` method. For example::
+
+ # Update all the headlings to the same value.
+ Entry.objects.all().update(headline='Everything is the same')
+
+You can only set non-relation fields and ``ForeignKey`` fields using this
+method and the value you set the field to must be a normal Python value (you
+can't set a field to be equal to some other field at the moment).
+
+To update ``ForeignKey`` fields, set the new value to be the new model
+instance you want to point to. Example::
+
+ b = Blog.objects.get(pk=1)
+ # Make all entries belong to this blog.
+ Entry.objects.all().update(blog=b)
+
+The ``update()`` method is applied instantly and doesn't return anything
+(similar to ``delete()``). The only restriction on the queryset that is
+updated is that it can only access one database table, the model's main
+table. So don't try to filter based on related fields or anything like that;
+it won't work.
+
Extra instance methods
======================
diff --git a/tests/modeltests/update/__init__.py b/tests/modeltests/update/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/update/__init__.py
diff --git a/tests/modeltests/update/models.py b/tests/modeltests/update/models.py
new file mode 100644
index 0000000000..55b30cd3f5
--- /dev/null
+++ b/tests/modeltests/update/models.py
@@ -0,0 +1,60 @@
+"""
+Tests for the update() queryset method that allows in-place, multi-object
+updates.
+"""
+
+from django.db import models
+
+class DataPoint(models.Model):
+ name = models.CharField(max_length=20)
+ value = models.CharField(max_length=20)
+ another_value = models.CharField(max_length=20, blank=True)
+
+ def __unicode__(self):
+ return unicode(self.name)
+
+class RelatedPoint(models.Model):
+ name = models.CharField(max_length=20)
+ data = models.ForeignKey(DataPoint)
+
+ def __unicode__(self):
+ return unicode(self.name)
+
+
+__test__ = {'API_TESTS': """
+>>> DataPoint(name="d0", value="apple").save()
+>>> DataPoint(name="d2", value="banana").save()
+>>> d3 = DataPoint(name="d3", value="banana")
+>>> d3.save()
+>>> RelatedPoint(name="r1", data=d3).save()
+
+Objects are updated by first filtering the candidates into a queryset and then
+calling the update() method. It executes immediately and returns nothing.
+
+>>> DataPoint.objects.filter(value="apple").update(name="d1")
+>>> DataPoint.objects.filter(value="apple")
+[<DataPoint: d1>]
+
+We can update multiple objects at once.
+
+>>> DataPoint.objects.filter(value="banana").update(value="pineapple")
+>>> DataPoint.objects.get(name="d2").value
+u'pineapple'
+
+Foreign key fields can also be updated, although you can only update the object
+referred to, not anything inside the related object.
+
+>>> d = DataPoint.objects.get(name="d1")
+>>> RelatedPoint.objects.filter(name="r1").update(data=d)
+>>> RelatedPoint.objects.filter(data__name="d1")
+[<RelatedPoint: r1>]
+
+Multiple fields can be updated at once
+
+>>> DataPoint.objects.filter(value="pineapple").update(value="fruit", another_value="peaches")
+>>> d = DataPoint.objects.get(name="d2")
+>>> d.value, d.another_value
+(u'fruit', u'peaches')
+
+"""
+}