Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

queryset-refactor: Added an update method to QuerySets, since it's ne…

…eded for

moving SQL out of the core code. Only direct fields and foreign keys can be
updated in this fashion, since multi-table updates are very non-portable.

This also cleans up the API for the UpdateQuery class a bit. Still need to
change DeleteQuery to work similarly, I suspect.

Refs #4260.


git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7043 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit de94d0cb9381bcab02b7be0de1d8ac577bcfab92 1 parent 911e65a
@malcolmt malcolmt authored
View
11 django/db/models/query.py
@@ -255,6 +255,17 @@ def delete(self):
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 #
##################################################
View
49 django/db/models/sql/query.py
@@ -174,6 +174,8 @@ def clone(self, klass=None, **kwargs):
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 @@ def as_sql(self):
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 @@ def clear_related(self, related_field, pk_list):
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):
"""
View
28 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
======================
View
0  tests/modeltests/update/__init__.py
No changes.
View
60 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')
+
+"""
+}
Please sign in to comment.
Something went wrong with that request. Please try again.