Index: django/db/models/query.py
===================================================================
--- django/db/models/query.py	(revision 7022)
+++ django/db/models/query.py	(working copy)
@@ -27,6 +27,10 @@
     'regex', 'iregex',
 )
 
+UPDATE_TERMS = (
+    'add', 'exact', 'sql', 'subtract',
+)
+
 # Size of each "chunk" for get_iterator calls.
 # Larger values are slightly faster at the expense of more storage space.
 GET_ITERATOR_CHUNK_SIZE = 100
@@ -319,6 +323,59 @@
         qs._params.extend(id_list)
         return dict([(obj._get_pk_val(), obj) for obj in qs.iterator()])
 
+    def update(self, **kwargs):
+        """
+        Performs a SQL UPDATE.
+        """
+        if not kwargs:
+            raise ValueError('No updates were specified.')
+
+        opts = self.model._meta
+        db_table = connection.ops.quote_name(opts.db_table)
+        set_, set_params = parse_update(kwargs.items(), opts)
+        joins, where, where_params = self._filters.get_sql(opts)
+
+        sql = ['UPDATE %s' % db_table]
+
+        if joins:
+            # Special case for databases which don't allow joins in
+            # UPDATE statements - select primary keys and add a new
+            # where IN clause.
+            if not connection.features.allows_join_in_update:
+                try:
+                    select, pk_sql, pk_params = self._get_sql_clause()
+                except EmptyResultSet:
+                    return
+
+                cursor = connection.cursor()
+                pk_col = '%s.%s' % (db_table,
+                                    connection.ops.quote_name(opts.pk.column))
+                cursor.execute('SELECT %s' % pk_col + pk_sql, pk_params)
+                pks = [row[0] for row in cursor.fetchall()]
+
+                # Remove join details from where and where_params
+                joins, where, where_params = get_non_join_sql(self._filters, opts)
+
+                try:
+                    where.append(get_where_clause('in', db_table + '.',
+                                                  opts.pk.column, pks,
+                                                  opts.pk.db_type()))
+                except EmptyResultSet:
+                    return
+                where_params.extend(opts.pk.get_db_prep_lookup('in', pks))
+            else:
+                sql.append(' '.join(['%s %s AS %s ON %s' % (join_type, table, alias, condition)
+                                     for (alias, (table, join_type, condition)) in joins.items()]))
+
+        sql.append('SET %s' % ','.join(set_))
+
+        if where:
+            sql.append('WHERE %s' % ' AND '.join(where))
+
+        cursor = connection.cursor()
+        cursor.execute(' '.join(sql), set_params + where_params)
+        transaction.commit_unless_managed()
+
     def delete(self):
         """
         Deletes the records in the current QuerySet.
@@ -929,6 +986,90 @@
         params.extend(params2)
     return joins, where, params
 
+def parse_update(kwarg_items, opts):
+    set_, params = [], []
+
+    for kwarg, value in kwarg_items:
+        path = kwarg.split(LOOKUP_SEPARATOR)
+        update_type = path.pop()
+        if len(path) == 0 or update_type not in UPDATE_TERMS:
+            path.append(update_type)
+            update_type = 'exact'
+
+        if len(path) < 1:
+            raise TypeError('Cannot parse keyword update %r') % kwarg
+
+        if value is None:
+            # Interpret '__exact=None' as the sql '= NULL'; otherwise, reject
+            # all uses of None as a query value.
+            if update_type != 'exact':
+                raise ValueError('Cannot use None as a query value.')
+        elif callable(value):
+            value = value()
+
+        field = opts.get_field(path[0], many_to_many=False)
+        db_field = connection.ops.quote_name(field.column)
+        if update_type == 'sql':
+            try:
+                sql, sql_params = value
+                set_.append('%s=%s' % (db_field, sql))
+                params.extend(sql_params)
+            except ValueError:
+                set_.append('%s=%s' % (db_field, value))
+        else:
+            if update_type == 'exact':
+                set_.append('%s=%%s' % db_field)
+            elif update_type == 'add':
+                set_.append('%s=%s+%%s' % (db_field, db_field))
+            elif update_type == 'subtract':
+                set_.append('%s=%s-%%s' % (db_field, db_field))
+            params.append(field.get_db_prep_save(value))
+    return set_, params
+
+def get_non_join_sql(q, opts):
+    # Here be dragyns
+    if isinstance(q, QNot):
+        try:
+            joins, where, params = get_non_join_sql(q.q, opts)
+            where2 = ['(NOT (%s))' % ' AND '.join(where)]
+        except EmptyResultSet:
+            return SortedDict(), [], []
+        return joins, where2, params
+    elif isinstance(q, Q):
+        return parse_lookup(exclude_joins(q.kwargs).items(), opts)
+    else:
+        joins, where, params = SortedDict(), [], []
+        for val in q.args:
+            try:
+                joins, where2, params2 = get_non_join_sql(val, opts)
+                where.extend(where2)
+                params.extend(params2)
+            except EmptyResultSet:
+                if not isinstance(q, QOr):
+                    raise EmptyResultSet
+        if where:
+            return joins, ['(%s)' % q.operator.join(where)], params
+        return joins, [], params
+
+def exclude_joins(kwargs):
+    """
+    Returns a dict of filters consisting of those in the given dict
+    which do not require joins. These are of the following form::
+
+    field_name
+    field_name__lookup
+    """
+    filters = {}
+    for kwarg, value in kwargs.items():
+        path = kwarg.split(LOOKUP_SEPARATOR)
+        if len(path) == 1:
+            filters[kwarg] = value
+        else:
+            lookup_type = path.pop()
+            if len(path) == 1 and (lookup_type == 'pk' or lookup_type in QUERY_TERMS):
+                filters[kwarg] = value
+    return filters
+
 class FieldFound(Exception):
     "Exception used to short circuit field-finding operations."
     pass
Index: django/db/backends/sqlite3/base.py
===================================================================
--- django/db/backends/sqlite3/base.py	(revision 7022)
+++ django/db/backends/sqlite3/base.py	(working copy)
@@ -39,6 +39,7 @@
 Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
 
 class DatabaseFeatures(BaseDatabaseFeatures):
+    allows_join_in_update = False
     supports_constraints = False
 
 class DatabaseOperations(BaseDatabaseOperations):
Index: django/db/backends/__init__.py
===================================================================
--- django/db/backends/__init__.py	(revision 7022)
+++ django/db/backends/__init__.py	(working copy)
@@ -41,6 +41,7 @@
 
 class BaseDatabaseFeatures(object):
     allows_group_by_ordinal = True
+    allows_join_in_update = True
     allows_unique_and_pk = True
     autoindexes_primary_keys = True
     inline_fk_references = True
