Code

Ticket #7210: 0001-Added-expression-support-for-QuerySet.update.5.patch

File 0001-Added-expression-support-for-QuerySet.update.5.patch, 23.8 KB (added by sebastian_noack, 6 years ago)
  • django/db/models/__init__.py

    From d0178268b5ec1f6313a2adca3bf97fcb94b5ef86 Mon Sep 17 00:00:00 2001
    From: Sebastian Noack <sebastian.noack@gmail.com>
    Date: Thu, 8 May 2008 14:30:19 +0200
    Subject: [PATCH] Added expression support for QuerySet.update.
    
    ---
     django/db/models/__init__.py             |    1 +
     django/db/models/query.py                |    2 +
     django/db/models/sql/expressions.py      |  158 ++++++++++++++++++++++++++++++
     django/db/models/sql/query.py            |    2 +-
     django/db/models/sql/subqueries.py       |   54 +++++------
     django/db/models/sql/where.py            |   49 +++++----
     tests/modeltests/expressions/models.py   |   63 ++++++++++++
     tests/modeltests/update/models.py        |   78 +++++++++------
     8 files changed, 324 insertions(+), 83 deletions(-)
     create mode 100644 django/db/models/sql/expressions.py
     create mode 100644 tests/modeltests/expressions/__init__.py
     create mode 100644 tests/modeltests/expressions/models.py
    
    diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
    index 86763d9..f5ab8eb 100644
    a b from django.core import validators 
    44from django.db import connection 
    55from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models 
    66from django.db.models.query import Q 
     7from django.db.models.sql.expressions import F 
    78from django.db.models.manager import Manager 
    89from django.db.models.base import Model, AdminOptions 
    910from django.db.models.fields import * 
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index 6b341ba..9cad340 100644
    a b class QuerySet(object): 
    292292        Updates all elements in the current QuerySet, setting all the given 
    293293        fields to the appropriate values. 
    294294        """ 
     295        assert self.query.can_filter(), \ 
     296                "Cannot update a query once a slice has been taken." 
    295297        query = self.query.clone(sql.UpdateQuery) 
    296298        query.add_update_values(kwargs) 
    297299        query.execute_sql(None) 
  • new file django/db/models/sql/expressions.py

    diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py
    new file mode 100644
    index 0000000..0a933f5
    - +  
     1from copy import deepcopy 
     2from datetime import datetime 
     3 
     4from django.utils import tree 
     5from django.core.exceptions import FieldError 
     6from django.db import connection 
     7from django.db.models.fields import Field, FieldDoesNotExist 
     8from django.db.models.query_utils import QueryWrapper 
     9 
     10class Expression(object): 
     11    """ 
     12    Base class for all sql expressions, expected by QuerySet.update. 
     13    """ 
     14    # Arithmetic connection types 
     15    ADD = '+' 
     16    SUB = '-' 
     17    MUL = '*' 
     18    DIV = '/' 
     19    MOD = '%%' 
     20 
     21    # Bitwise connection types 
     22    AND = '&' 
     23    OR = '|' 
     24 
     25    def _combine(self, other, conn, reversed, node=None): 
     26        if reversed: 
     27            obj = ExpressionNode([Literal(other)], conn) 
     28            obj.add(node or self, conn) 
     29        else: 
     30            obj = node or ExpressionNode([self], conn) 
     31            if isinstance(other, Expression): 
     32                obj.add(other, conn) 
     33            else: 
     34                obj.add(Literal(other), conn) 
     35        return obj 
     36 
     37    def __add__(self, other): 
     38        return self._combine(other, self.ADD, False) 
     39 
     40    def __sub__(self, other): 
     41        return self._combine(other, self.SUB, False) 
     42 
     43    def __mul__(self, other): 
     44        return self._combine(other, self.MUL, False) 
     45 
     46    def __div__(self, other): 
     47        return self._combine(other, self.DIV, False) 
     48 
     49    def __mod__(self, other): 
     50        return self._combine(other, self.MOD, False) 
     51 
     52    def __and__(self, other): 
     53        return self._combine(other, self.AND, False) 
     54 
     55    def __or__(self, other): 
     56        return self._combine(other, self.OR, False) 
     57 
     58    def __radd__(self, other): 
     59        return self._combine(other, self.ADD, True) 
     60 
     61    def __rsub__(self, other): 
     62        return self._combine(other, self.SUB, True) 
     63 
     64    def __rmul__(self, other): 
     65        return self._combine(other, self.MUL, True) 
     66 
     67    def __rdiv__(self, other): 
     68        return self._combine(other, self.DIV, True) 
     69 
     70    def __rmod__(self, other): 
     71        return self._combine(other, self.MOD, True) 
     72 
     73    def __rand__(self, other): 
     74        return self._combine(other, self.AND, True) 
     75 
     76    def __ror__(self, other): 
     77        return self._combine(other, self.OR, True) 
     78 
     79    def as_sql(self, opts, field, prep_func=None, qn=None): 
     80        raise NotImplementedError 
     81 
     82class ExpressionNode(Expression, tree.Node): 
     83    default = None 
     84 
     85    def __init__(self, children=None, connector=None, negated=False): 
     86        if children is not None and len(children) > 1 and connector is None: 
     87            raise TypeError('You have to specify a connector.') 
     88        super(ExpressionNode, self).__init__(children, connector, negated) 
     89 
     90    def _combine(self, *args, **kwargs): 
     91        return super(ExpressionNode, self)._combine(node=deepcopy(self), *args, **kwargs) 
     92 
     93    def as_sql(self, opts, field, prep_func=None, qn=None, node=None): 
     94        if not qn: 
     95            qn = connection.ops.quote_name 
     96        if node is None: 
     97            node = self 
     98 
     99        result = [] 
     100        result_params = [] 
     101        for child in node.children: 
     102            if hasattr(child, 'as_sql'): 
     103                sql, params = child.as_sql(opts, field, prep_func, qn) 
     104                format = '%s' 
     105            else: 
     106                sql, params = self.as_sql(opts, field, prep_func, qn, child) 
     107                if len(child.children) > 1: 
     108                    format = '(%s)' 
     109                else: 
     110                    format = '%s' 
     111            if sql: 
     112                result.append(format % sql) 
     113                result_params.extend(params) 
     114        conn = ' %s ' % node.connector 
     115        return conn.join(result), result_params 
     116 
     117class Literal(Expression): 
     118    """ 
     119    An expression representing the given value. 
     120    """ 
     121    def __init__(self, value): 
     122        self.value = value 
     123 
     124    def as_sql(self, opts, field, prep_func=None, qn=None): 
     125        if self.value is None: 
     126            return 'NULL', () 
     127 
     128        if isinstance(self.value, datetime): 
     129            sql = connection.ops.datetime_cast_sql() 
     130        else: 
     131            sql = '%s' 
     132        params = prep_func and prep_func(self.value) or (self.value,) 
     133 
     134        if isinstance(params, QueryWrapper): 
     135            return params.data 
     136        return sql, params 
     137 
     138class F(Expression): 
     139    """ 
     140    An expression representing the value of the given field. 
     141    """ 
     142    def __init__(self, name): 
     143        self.name = name 
     144 
     145    def as_sql(self, opts, field, prep_func=None, qn=None): 
     146        if not qn: 
     147            qn = connection.ops.quote_name 
     148 
     149        try: 
     150            src_field = opts.get_field(self.name) 
     151        except FieldDoesNotExist: 
     152            names = opts.get_all_field_names() 
     153            raise FieldError('Cannot resolve keyword %r into field. ' 
     154                    'Choices are: %s' % (self.name, ', '.join(names))) 
     155 
     156        field_sql = connection.ops.field_cast_sql(src_field.db_type()) 
     157        lhs = '%s.%s' % (qn(opts.db_table), qn(src_field.column)) 
     158        return field_sql % lhs, () 
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    index a6957ba..bd1d030 100644
    a b class Query(object): 
    250250        # get_from_clause() for details. 
    251251        from_, f_params = self.get_from_clause() 
    252252 
    253         where, w_params = self.where.as_sql(qn=self.quote_name_unless_alias) 
     253        where, w_params = self.where.as_sql(self.get_meta(), qn=self.quote_name_unless_alias) 
    254254        params = list(self.extra_select_params) 
    255255 
    256256        result = ['SELECT'] 
  • django/db/models/sql/subqueries.py

    diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
    index 7385cd0..c23a361 100644
    a b from django.db.models.sql.constants import * 
    88from django.db.models.sql.datastructures import RawValue, Date 
    99from django.db.models.sql.query import Query 
    1010from django.db.models.sql.where import AND 
     11from django.db.models.sql.expressions import Expression, Literal 
    1112 
    1213__all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery', 
    1314        'CountQuery'] 
    class DeleteQuery(Query): 
    2526        assert len(self.tables) == 1, \ 
    2627                "Can only delete from one table at a time." 
    2728        result = ['DELETE FROM %s' % self.quote_name_unless_alias(self.tables[0])] 
    28         where, params = self.where.as_sql() 
     29        where, params = self.where.as_sql(self.get_meta()) 
    2930        result.append('WHERE %s' % where) 
    3031        return ' '.join(result), tuple(params) 
    3132 
    class UpdateQuery(Query): 
    126127        result = ['UPDATE %s' % qn(table)] 
    127128        result.append('SET') 
    128129        values, update_params = [], [] 
    129         for name, val, placeholder in self.values: 
    130             if val is not None: 
    131                 values.append('%s = %s' % (qn(name), placeholder)) 
    132                 update_params.append(val) 
    133             else: 
    134                 values.append('%s = NULL' % qn(name)) 
     130        for name, sql, params in self.values: 
     131            values.append('%s = %s' % (qn(name), sql)) 
     132            update_params.extend(params) 
    135133        result.append(', '.join(values)) 
    136         where, params = self.where.as_sql() 
     134        where, params = self.where.as_sql(self.get_meta()) 
    137135        if where: 
    138136            result.append('WHERE %s' % where) 
    139137        return ' '.join(result), tuple(update_params + params) 
    class UpdateQuery(Query): 
    207205            self.where.add((None, f.column, f, 'in', 
    208206                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), 
    209207                    AND) 
    210             self.values = [(related_field.column, None, '%s')] 
     208            self.values = [(related_field.column, 'NULL', ())] 
    211209            self.execute_sql(None) 
    212210 
    213211    def add_update_values(self, values): 
    class UpdateQuery(Query): 
    232230        """ 
    233231        from django.db.models.base import Model 
    234232        for field, model, val in values_seq: 
    235             # FIXME: Some sort of db_prep_* is probably more appropriate here. 
    236             if field.rel and isinstance(val, Model): 
    237                 val = val.pk 
    238  
    239             # Getting the placeholder for the field. 
    240             if hasattr(field, 'get_placeholder'): 
    241                 placeholder = field.get_placeholder(val) 
     233            if isinstance(val, Expression): 
     234                expr = val 
    242235            else: 
    243                 placeholder = '%s' 
     236                expr = Literal(val) 
     237 
     238            sql, params = expr.as_sql( 
     239                self.get_meta(), 
     240                field, 
     241                lambda x: field.get_db_prep_lookup('exact', field.get_db_prep_save(x)), 
     242                self.connection.ops.quote_name) 
    244243 
    245244            if model: 
    246                 self.add_related_update(model, field.column, val, placeholder) 
     245                self.add_related_update(model, field.column, sql, params) 
    247246            else: 
    248                 self.values.append((field.column, val, placeholder)) 
     247                self.values.append((field.column, sql, params)) 
    249248 
    250     def add_related_update(self, model, column, value, placeholder): 
     249    def add_related_update(self, model, column, sql, params): 
    251250        """ 
    252251        Adds (name, value) to an update query for an ancestor model. 
    253252 
    254253        Updates are coalesced so that we only run one update query per ancestor. 
    255254        """ 
    256255        try: 
    257             self.related_updates[model].append((column, value, placeholder)) 
     256            self.related_updates[model].append((column, sql, params)) 
    258257        except KeyError: 
    259             self.related_updates[model] = [(column, value, placeholder)] 
     258            self.related_updates[model] = [(column, sql, params)] 
    260259 
    261260    def get_related_updates(self): 
    262261        """ 
    class InsertQuery(Query): 
    312311        parameters. This provides a way to insert NULL and DEFAULT keywords 
    313312        into the query, for example. 
    314313        """ 
    315         placeholders, values = [], [] 
     314        values = [] 
    316315        for field, val in insert_values: 
    317             if hasattr(field, 'get_placeholder'): 
    318                 # Some fields (e.g. geo fields) need special munging before 
    319                 # they can be inserted. 
    320                 placeholders.append(field.get_placeholder(val)) 
    321             else: 
    322                 placeholders.append('%s') 
    323  
    324316            self.columns.append(field.column) 
    325317            values.append(val) 
    326318        if raw_values: 
    327319            self.values.extend(values) 
    328320        else: 
    329321            self.params += tuple(values) 
    330             self.values.extend(placeholders) 
     322            self.values.extend(['%s'] * len(values)) 
    331323 
    332324class DateQuery(Query): 
    333325    """ 
  • django/db/models/sql/where.py

    diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
    index 3e8bfed..778a95e 100644
    a b Code to manage the creation and SQL rendering of 'where' constraints. 
    44import datetime 
    55 
    66from django.utils import tree 
     7from django.utils.functional import curry 
    78from django.db import connection 
    89from django.db.models.fields import Field 
    910from django.db.models.query_utils import QueryWrapper 
     11from django.db.models.sql.expressions import Expression, Literal 
    1012from datastructures import EmptyResultSet, FullResultSet 
    1113 
    1214# Connection types 
    class WhereNode(tree.Node): 
    2628    """ 
    2729    default = AND 
    2830 
    29     def as_sql(self, node=None, qn=None): 
     31    def as_sql(self, opts, node=None, qn=None): 
    3032        """ 
    3133        Returns the SQL version of the where clause and the value to be 
    3234        substituted in. Returns None, None if this node is empty. 
    class WhereNode(tree.Node): 
    4749        for child in node.children: 
    4850            try: 
    4951                if hasattr(child, 'as_sql'): 
    50                     sql, params = child.as_sql(qn=qn) 
     52                    sql, params = child.as_sql(opts, qn=qn) 
    5153                    format = '(%s)' 
    5254                elif isinstance(child, tree.Node): 
    53                     sql, params = self.as_sql(child, qn) 
     55                    sql, params = self.as_sql(opts, child, qn) 
    5456                    if child.negated: 
    5557                        format = 'NOT (%s)' 
    5658                    elif len(child.children) == 1: 
    class WhereNode(tree.Node): 
    5860                    else: 
    5961                        format = '(%s)' 
    6062                else: 
    61                     sql, params = self.make_atom(child, qn) 
     63                    sql, params = self.make_atom(opts, child, qn) 
    6264                    format = '%s' 
    6365            except EmptyResultSet: 
    6466                if node.connector == AND and not node.negated: 
    class WhereNode(tree.Node): 
    8688        conn = ' %s ' % node.connector 
    8789        return conn.join(result), result_params 
    8890 
    89     def make_atom(self, child, qn): 
     91    def make_atom(self, opts, child, qn): 
    9092        """ 
    9193        Turn a tuple (table_alias, field_name, field_class, lookup_type, value) 
    9294        into valid SQL. 
    class WhereNode(tree.Node): 
    99101            lhs = '%s.%s' % (qn(table_alias), qn(name)) 
    100102        else: 
    101103            lhs = qn(name) 
     104        if not field: 
     105            field = Field() 
    102106        db_type = field and field.db_type() or None 
    103107        field_sql = connection.ops.field_cast_sql(db_type) % lhs 
     108        prep_func = curry(field.get_db_prep_lookup, lookup_type) 
    104109 
    105         if isinstance(value, datetime.datetime): 
    106             cast_sql = connection.ops.datetime_cast_sql() 
    107         else: 
    108             cast_sql = '%s' 
     110        if lookup_type in connection.operators: 
     111            if isinstance(value, Expression): 
     112                sql, params = value.as_sql(opts, field, prep_func, qn) 
     113            else: 
     114                sql, params = Literal(value).as_sql(opts, field, prep_func, qn) 
    109115 
    110         if field: 
    111             params = field.get_db_prep_lookup(lookup_type, value) 
    112         else: 
    113             params = Field().get_db_prep_lookup(lookup_type, value) 
     116            format = '%s %s' % ( 
     117                connection.ops.lookup_cast(lookup_type), 
     118                connection.operators[lookup_type]) 
     119            return format % (field_sql, sql), params 
     120 
     121        if isinstance(value, Expression): 
     122            raise TypeError('Invalid lookup_type for use with %s object: %r' % ( 
     123                value.__class__.__name__, lookup_type)) 
     124 
     125        params = prep_func(value) 
    114126        if isinstance(params, QueryWrapper): 
    115127            extra, params = params.data 
    116128        else: 
    117129            extra = '' 
    118130 
    119         if lookup_type in connection.operators: 
    120             format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type), 
    121                     extra) 
    122             return (format % (field_sql, 
    123                     connection.operators[lookup_type] % cast_sql), params) 
    124  
    125131        if lookup_type == 'in': 
    126132            if not value: 
    127133                raise EmptyResultSet 
    128134            if extra: 
    129135                return ('%s IN %s' % (field_sql, extra), params) 
    130             return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))), 
    131                     params) 
     136            return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))), params) 
    132137        elif lookup_type in ('range', 'year'): 
    133138            return ('%s BETWEEN %%s and %%s' % field_sql, params) 
    134139        elif lookup_type in ('month', 'day'): 
    class EverythingNode(object): 
    164169    """ 
    165170    A node that matches everything. 
    166171    """ 
    167     def as_sql(self, qn=None): 
     172    def as_sql(self, opts, qn=None): 
    168173        raise FullResultSet 
    169174 
    170175    def relabel_aliases(self, change_map, node=None): 
  • new file tests/modeltests/expressions/models.py

    diff --git a/tests/modeltests/expressions/__init__.py b/tests/modeltests/expressions/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py
    new file mode 100644
    index 0000000..0e58069
    - +  
     1""" 
     2Tests for the update() queryset method that allows in-place, multi-object 
     3updates. 
     4""" 
     5 
     6from django.db import models 
     7 
     8class Number(models.Model): 
     9    integer = models.IntegerField() 
     10    float = models.FloatField(null=True) 
     11 
     12    def __unicode__(self): 
     13        return u'%i, %.3f' % (self.integer, self.float) 
     14 
     15 
     16__test__ = {'API_TESTS': """ 
     17>>> from django.db.models import F 
     18 
     19>>> Number(integer=-1).save() 
     20>>> Number(integer=42).save() 
     21>>> Number(integer=1337).save() 
     22 
     23We can fill a value in all objects with an other value of the same object. 
     24 
     25>>> Number.objects.update(float=F('integer')) 
     26>>> Number.objects.all() 
     27[<Number: -1, -1.000>, <Number: 42, 42.000>, <Number: 1337, 1337.000>] 
     28 
     29We can increment a value of all objects in a query set. 
     30 
     31>>> Number.objects.filter(integer__gt=0).update(integer=F('integer') + 1) 
     32>>> Number.objects.all() 
     33[<Number: -1, -1.000>, <Number: 43, 42.000>, <Number: 1338, 1337.000>] 
     34 
     35We can filter for objects, where a value is not equals the value of an other field. 
     36 
     37>>> Number.objects.exclude(float=F('integer')) 
     38[<Number: 43, 42.000>, <Number: 1338, 1337.000>] 
     39 
     40Complex expressions of different connection types are possible. 
     41 
     42>>> n = Number.objects.create(integer=10, float=123.45) 
     43>>> Number.objects.filter(pk=n.pk).update(float=F('integer') + F('float') * 2) 
     44>>> Number.objects.get(pk=n.pk) 
     45<Number: 10, 256.900> 
     46 
     47All supported operators, work as expected in native and reverse order. 
     48 
     49>>> from operator import add, sub, mul, div, mod, and_, or_ 
     50>>> for op in (add, sub, mul, div, mod, and_, or_): 
     51...     n = Number.objects.create(integer=42, float=15.) 
     52...     Number.objects.filter(pk=n.pk).update( 
     53...         integer=op(F('integer'), 15), float=op(42., F('float'))) 
     54...     Number.objects.get(pk=n.pk) 
     55<Number: 57, 57.000> 
     56<Number: 27, 27.000> 
     57<Number: 630, 630.000> 
     58<Number: 3, 2.800> 
     59<Number: 12, 12.000> 
     60<Number: 10, 10.000> 
     61<Number: 47, 47.000> 
     62 
     63"""} 
  • tests/modeltests/update/models.py

    diff --git a/tests/modeltests/update/models.py b/tests/modeltests/update/models.py
    index 3b0f833..3babc09 100644
    a b updates. 
    44""" 
    55 
    66from django.db import models 
     7from django.conf import settings 
    78 
    8 class DataPoint(models.Model): 
     9class Product(models.Model): 
    910    name = models.CharField(max_length=20) 
    10     value = models.CharField(max_length=20) 
    11     another_value = models.CharField(max_length=20, blank=True) 
     11    description = models.CharField(max_length=20) 
     12    expires = models.DateTimeField(null=True) 
    1213 
    1314    def __unicode__(self): 
    1415        return unicode(self.name) 
    1516 
    16 class RelatedPoint(models.Model): 
     17class RelatedProduct(models.Model): 
    1718    name = models.CharField(max_length=20) 
    18     data = models.ForeignKey(DataPoint) 
     19    data = models.ForeignKey(Product) 
    1920 
    2021    def __unicode__(self): 
    2122        return unicode(self.name) 
    2223 
    2324 
    2425__test__ = {'API_TESTS': """ 
    25 >>> DataPoint(name="d0", value="apple").save() 
    26 >>> DataPoint(name="d2", value="banana").save() 
    27 >>> d3 = DataPoint(name="d3", value="banana") 
    28 >>> d3.save() 
    29 >>> RelatedPoint(name="r1", data=d3).save() 
     26>>> from datetime import datetime 
     27 
     28>>> Product(name="p0", description="apple").save() 
     29>>> Product(name="p2", description="banana").save() 
     30>>> p3 = Product(name="p3", description="banana") 
     31>>> p3.save() 
     32>>> RelatedProduct(name="r1", data=p3).save() 
    3033 
    3134Objects are updated by first filtering the candidates into a queryset and then 
    3235calling the update() method. It executes immediately and returns nothing. 
    3336 
    34 >>> DataPoint.objects.filter(value="apple").update(name="d1") 
    35 >>> DataPoint.objects.filter(value="apple") 
    36 [<DataPoint: d1>] 
     37>>> Product.objects.filter(description="apple").update(name="p1") 
     38>>> Product.objects.filter(description="apple") 
     39[<Product: p1>] 
    3740 
    3841We can update multiple objects at once. 
    3942 
    40 >>> DataPoint.objects.filter(value="banana").update(value="pineapple") 
    41 >>> DataPoint.objects.get(name="d2").value 
     43>>> Product.objects.filter(description="banana").update(description="pineapple") 
     44>>> Product.objects.get(name="p2").description 
    4245u'pineapple' 
    4346 
    4447Foreign key fields can also be updated, although you can only update the object 
    4548referred to, not anything inside the related object. 
    4649 
    47 >>> d = DataPoint.objects.get(name="d1") 
    48 >>> RelatedPoint.objects.filter(name="r1").update(data=d) 
    49 >>> RelatedPoint.objects.filter(data__name="d1") 
    50 [<RelatedPoint: r1>] 
     50>>> p = Product.objects.get(name="p1") 
     51>>> RelatedProduct.objects.filter(name="r1").update(data=p) 
     52>>> RelatedProduct.objects.filter(data__name="p1") 
     53[<RelatedProduct: r1>] 
     54 
     55Multiple fields can be updated at once. If DATABASE_ENGINE is mysql microseconds 
     56must be truncated. 
     57 
     58>>> Product.objects.filter(description="pineapple").update( 
     59...     description="fruit", 
     60...     expires=datetime(2010, 1, 1, 12, 0, 0, 123456)) 
     61>>> p = Product.objects.get(name="p2") 
     62>>> p.description, p.expires 
     63"""} 
    5164 
    52 Multiple fields can be updated at once 
     65if settings.DATABASE_ENGINE == 'mysql': 
     66    __test__['API_TESTS'] += "(u'fruit', datetime.datetime(2010, 1, 1, 12, 0))" 
     67else: 
     68    __test__['API_TESTS'] += "(u'fruit', datetime.datetime(2010, 1, 1, 12, 0, 0, 123456))" 
    5369 
    54 >>> DataPoint.objects.filter(value="pineapple").update(value="fruit", another_value="peaches") 
    55 >>> d = DataPoint.objects.get(name="d2") 
    56 >>> d.value, d.another_value 
    57 (u'fruit', u'peaches') 
     70__test__['API_TESTS'] += """ 
    5871 
    5972In the rare case you want to update every instance of a model, update() is also 
    60 a manager method. 
     73a manager method and update with None works as well. 
     74 
     75>>> Product.objects.update(expires=None) 
     76>>> Product.objects.values('expires').distinct() 
     77[{'expires': None}] 
     78 
     79We can NOT update sliced query sets and that must be so. 
    6180 
    62 >>> DataPoint.objects.update(value='thing') 
    63 >>> DataPoint.objects.values('value').distinct() 
    64 [{'value': u'thing'}] 
     81>>> try: 
     82...     Product.objects.all()[:2].update(expires=datetime(2008, 1, 1)) 
     83... except Exception, e: 
     84...     print repr(e) 
     85AssertionError('Cannot update a query once a slice has been taken.',) 
    6586 
    6687""" 
    67 }