Django

Code

Ticket #7210: 7210-F-Syntax.patch

File 7210-F-Syntax.patch, 20.0 kB (added by nicolas, 2 years ago)

Re-write of the patch. Added docs.

  • django/db/models/sql/where.py

    old new  
    2626    relabel_aliases() methods. 
    2727    """ 
    2828    default = AND 
    29  
     29     
    3030    def add(self, data, connector): 
    3131        """ 
    3232        Add a node to the where-tree. If the data is a list or tuple, it is 
     
    4343            return 
    4444 
    4545        alias, col, field, lookup_type, value = data 
     46 
     47        #If the object defined its own value trust it's sql fragment to allow 
     48        #not inclusion of columns and avoid quoting 
     49        #import pdb; pdb.set_trace() 
     50        if hasattr(value, 'make_value'): 
     51            self.fragment, params = value.as_sql() 
     52            db_type = None 
     53        elif field: 
     54            params = field.get_db_prep_lookup(lookup_type, value) 
     55            db_type = field.db_type() 
     56        else: 
     57            # This is possible when we add a comparison to NULL sometimes (we 
     58            # don't really need to waste time looking up the associated field 
     59            # object). 
     60            params = Field().get_db_prep_lookup(lookup_type, value) 
     61            db_type = None 
     62 
    4663        try: 
    4764            if field: 
    4865                params = field.get_db_prep_lookup(lookup_type, value) 
     
    5976            # match. 
    6077            super(WhereNode, self).add(NothingNode(), connector) 
    6178            return 
     79 
    6280        if isinstance(value, datetime.datetime): 
    6381            annotation = datetime.datetime 
    6482        else: 
    6583            annotation = bool(value) 
     84 
    6685        super(WhereNode, self).add((alias, col, db_type, lookup_type, 
    6786                annotation, params), connector) 
    6887 
    69     def as_sql(self, qn=None): 
     88    def as_sql(self, qn=None, trust_content=False): 
    7089        """ 
    7190        Returns the SQL version of the where clause and the value to be 
    7291        substituted in. Returns None, None if this node is empty. 
     
    83102        result_params = [] 
    84103        empty = True 
    85104        for child in self.children: 
     105            #import pdb; pdb.set_trace() 
    86106            try: 
    87107                if hasattr(child, 'as_sql'): 
    88                     sql, params = child.as_sql(qn=qn) 
     108                    try: 
     109                        sql, params = child.as_sql(qn=qn, 
     110                                                   trust_content=hasattr(self, 'fragment') 
     111                                                   or trust_content) 
     112                    except: 
     113                        sql, params = child.as_sql(qn=qn) 
    89114                else: 
    90115                    # A leaf node in the tree. 
    91                     sql, params = self.make_atom(child, qn) 
     116                    sql, params = self.make_atom(child, qn, 
     117                                                 trust_content=hasattr(self, 'fragment') 
     118                                                 or trust_content) 
     119 
    92120            except EmptyResultSet: 
    93121                if self.connector == AND and not self.negated: 
    94122                    # We can bail out early in this particular case (only). 
     
    106134                if self.negated: 
    107135                    empty = True 
    108136                continue 
     137            if hasattr(self, 'fragment') and '%s' in sql: 
     138                sql = sql % self.fragment 
     139                 
    109140            empty = False 
    110141            if sql: 
    111142                result.append(sql) 
     
    122153                sql_string = '(%s)' % sql_string 
    123154        return sql_string, result_params 
    124155 
    125     def make_atom(self, child, qn): 
     156    def make_atom(self, child, qn, trust_content=False): 
    126157        """ 
    127158        Turn a tuple (table_alias, column_name, db_type, lookup_type, 
    128159        value_annot, params) into valid SQL. 
     
    141172            cast_sql = connection.ops.datetime_cast_sql() 
    142173        else: 
    143174            cast_sql = '%s' 
    144  
     175             
    145176        if isinstance(params, QueryWrapper): 
    146177            extra, params = params.data 
    147178        else: 
    148179            extra = '' 
    149180 
     181        if trust_content: 
     182            extra = '' 
     183 
    150184        if lookup_type in connection.operators: 
     185            #REm0ove %s 
    151186            format = "%s %%s %s" % (connection.ops.lookup_cast(lookup_type), 
    152187                    extra) 
    153188            return (format % (field_sql, 
     
    175210 
    176211        raise TypeError('Invalid lookup_type: %r' % lookup_type) 
    177212 
     213 
    178214    def relabel_aliases(self, change_map, node=None): 
    179215        """ 
    180216        Relabels the alias values of any children. 'change_map' is a dictionary 
  • django/db/models/sql/expressions.py

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

    old new  
    10391039        opts = self.get_meta() 
    10401040        alias = self.get_initial_alias() 
    10411041        allow_many = trim or not negate 
    1042  
     1042         
    10431043        try: 
    10441044            field, target, opts, join_list, last = self.setup_joins(parts, opts, 
    10451045                    alias, True, allow_many, can_reuse=can_reuse) 
     
    11141114                if self.promote_alias(table, table_promote): 
    11151115                    table_promote = True 
    11161116 
     1117        #Manage special objects through their make_value attribute 
     1118        if hasattr(value, 'make_value'): 
     1119            value.make_value((opts, self.select)) 
    11171120        self.where.add((alias, col, field, lookup_type, value), connector) 
    1118  
     1121         
    11191122        if negate: 
    11201123            for alias in join_list: 
    11211124                self.promote_alias(alias) 
     
    11371140                    entry.add((alias, col, None, 'isnull', True), AND) 
    11381141                    entry.negate() 
    11391142                    self.where.add(entry, AND) 
    1140  
     1143                     
    11411144        if can_reuse is not None: 
    11421145            can_reuse.update(join_list) 
    11431146 
     
    16251628                return 
    16261629 
    16271630        cursor = self.connection.cursor() 
     1631        #import pdb; pdb.set_trace() 
    16281632        cursor.execute(sql, params) 
    16291633 
    16301634        if not result_type: 
  • django/db/models/sql/subqueries.py

    old new  
    137137        for name, val, placeholder in self.values: 
    138138            if val is not None: 
    139139                values.append('%s = %s' % (qn(name), placeholder)) 
    140                 update_params.append(val) 
     140                if not hasattr(val, 'make_value'): 
     141                    update_params.append(val) 
    141142            else: 
    142143                values.append('%s = NULL' % qn(name)) 
    143144        result.append(', '.join(values)) 
     
    239240        saving models. 
    240241        """ 
    241242        from django.db.models.base import Model 
     243        #import pdb; pdb.set_trace() 
    242244        for field, model, val in values_seq: 
    243245            # FIXME: Some sort of db_prep_* is probably more appropriate here. 
    244246            if field.rel and isinstance(val, Model): 
     
    250252            else: 
    251253                placeholder = '%s' 
    252254 
     255            if hasattr(val, 'make_value'): 
     256                val.make_value((self.get_meta(),self.select)) 
     257                fragment, _ = val.as_sql() 
     258                placeholder = placeholder % fragment 
     259                 
    253260            if model: 
    254261                self.add_related_update(model, field.column, val, placeholder) 
    255262            else: 
  • django/db/models/__init__.py

    old new  
    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 
    910from django.db.models.fields import * 
  • django/db/models/query_utils.py

    old new  
    66""" 
    77 
    88from copy import deepcopy 
    9  
     9from django.db import connection 
    1010from django.utils import tree 
    1111 
    1212class QueryWrapper(object): 
  • tests/modeltests/expressions/models.py

    old new  
     1""" 
     2Tests for the update() queryset method that allows in-place, multi-object 
     3updates. 
     4""" 
     5 
     6from django.db import models 
     7 
     8# 
     9# Model for testing arithmetic expressions. 
     10# 
     11 
     12class Number(models.Model): 
     13    integer = models.IntegerField() 
     14    float = models.FloatField(null=True) 
     15 
     16    def __unicode__(self): 
     17        return u'%i, %.3f' % (self.integer, self.float) 
     18 
     19# 
     20# A more ordinary use case. 
     21# 
     22 
     23class Employee(models.Model): 
     24    firstname = models.CharField(max_length=50) 
     25    lastname = models.CharField(max_length=50) 
     26 
     27    def __unicode__(self): 
     28        return u'%s %s' % (self.firstname, self.lastname) 
     29 
     30class Company(models.Model): 
     31    name = models.CharField(max_length=100) 
     32    num_employees = models.PositiveIntegerField() 
     33    num_chairs = models.PositiveIntegerField() 
     34    ceo = models.ForeignKey( 
     35        Employee, 
     36        related_name='company_ceo_set') 
     37    point_of_contact = models.ForeignKey( 
     38        Employee, 
     39        related_name='company_point_of_contact_set', 
     40        null=True) 
     41 
     42    def __unicode__(self): 
     43        return self.name 
     44 
     45 
     46__test__ = {'API_TESTS': """ 
     47>>> from django.db.models import F 
     48 
     49>>> Number(integer=-1).save() 
     50>>> Number(integer=42).save() 
     51>>> Number(integer=1337).save() 
     52 
     53We can fill a value in all objects with an other value of the same object. 
     54 
     55>>> Number.objects.update(float=F('integer')) 
     563 
     57>>> Number.objects.all() 
     58[<Number: -1, -1.000>, <Number: 42, 42.000>, <Number: 1337, 1337.000>] 
     59 
     60We can increment a value of all objects in a query set. 
     61 
     62>>> Number.objects.filter(integer__gt=0).update(integer=F('integer') + 1) 
     632 
     64>>> Number.objects.all() 
     65[<Number: -1, -1.000>, <Number: 43, 42.000>, <Number: 1338, 1337.000>] 
     66 
     67We can filter for objects, where a value is not equals the value of an other field. 
     68 
     69>>> Number.objects.exclude(float=F('integer')) 
     70[<Number: 43, 42.000>, <Number: 1338, 1337.000>] 
     71 
     72Complex expressions of different connection types are possible. 
     73 
     74>>> n = Number.objects.create(integer=10, float=123.45) 
     75 
     76>>> Number.objects.filter(pk=n.pk).update(float=F('integer') + F('float') * 2) 
     771 
     78>>> Number.objects.get(pk=n.pk) 
     79<Number: 10, 256.900> 
     80 
     81All supported operators, work as expected in native and reverse order. 
     82 
     83>>> from operator import add, sub, mul, div, mod, and_, or_ 
     84>>> for op in (add, sub, mul, div, mod, and_, or_): 
     85...     n = Number.objects.create(integer=42, float=15.) 
     86...     Number.objects.filter(pk=n.pk).update( 
     87...         integer=op(F('integer'), 15), float=op(42., F('float'))) 
     88...     Number.objects.get(pk=n.pk) 
     891 
     90<Number: 57, 57.000> 
     911 
     92<Number: 27, 27.000> 
     931 
     94<Number: 630, 630.000> 
     951 
     96<Number: 2, 2.800> 
     971 
     98<Number: 12, 12.000> 
     991 
     100<Number: 10, 10.000> 
     1011 
     102<Number: 47, 47.000> 
     103 
     104 
     105>>> Company(name='Example Inc.', num_employees=2300, num_chairs=5, 
     106...     ceo=Employee.objects.create(firstname='Joe', lastname='Smith')).save() 
     107>>> Company(name='Foobar Ltd.', num_employees=3, num_chairs=3, 
     108...     ceo=Employee.objects.create(firstname='Frank', lastname='Meyer')).save() 
     109>>> Company(name='Test GmbH', num_employees=32, num_chairs=1, 
     110...     ceo=Employee.objects.create(firstname='Max', lastname='Mustermann')).save() 
     111 
     112We can filter for companies where the number of employees is greater than the 
     113number of chairs. 
     114 
     115>>> Company.objects.filter(num_employees__gt=F('num_chairs')) 
     116[<Company: Example Inc.>, <Company: Test GmbH>] 
     117 
     118The relation of a foreign key can become copied over to an other foreign key. 
     119 
     120>>> Company.objects.update(point_of_contact=F('ceo')) 
     1213 
     122>>> [c.point_of_contact for c in Company.objects.all()] 
     123[<Employee: Joe Smith>, <Employee: Frank Meyer>, <Employee: Max Mustermann>] 
     124 
     125"""} 
  • docs/db-api.txt

    old new  
    3030        headline = models.CharField(max_length=255) 
    3131        body_text = models.TextField() 
    3232        pub_date = models.DateTimeField() 
     33        num_comments = models.IntegerField() 
     34        comment_threshold = models.IntegerField() 
    3335        authors = models.ManyToManyField(Author) 
    3436 
    3537        def __unicode__(self): 
     
    17441746    Blog.objetcs.filter(entry__author__isnull=False, 
    17451747            entry__author__name__isnull=True) 
    17461748 
     1749Using other model attributes in your lookups with F objects 
     1750----------------------------------------------------------- 
     1751 
     1752**New in Django development version** 
     1753 
     1754When filtering you can refer to you can refer to other attributes of 
     1755your model by using F objects and expressions.  For example if you 
     1756want to obtain the entries that have more comments than they were 
     1757suposed to you could do:: 
     1758 
     1759    Entry.objects.filter(num_comments__gt=F('comment_threshold')) 
     1760 
     1761You could also use aritmethic expressions so you could get those that 
     1762have doubled the expected threshold:: 
     1763 
     1764    Entry.objects.filter(num_comments__gt=2*F('comment_threshold')) 
     1765 
     1766The available operands for arithmetic expressions are:: 
     1767 
     1768 * Addition using ``+`` 
     1769 
     1770 * Substraction using ``-`` 
     1771 
     1772 * Multiplication using ``*`` 
     1773 
     1774 * Division using ``/`` 
     1775 
     1776 * Modulo using ``%%`` 
     1777  
     1778 * Bitwise AND using ``&`` 
     1779 
     1780 * Bitwise OR using ``|`` 
     1781 
     1782.. note::  
     1783   F objects can only access one database table, the model's main 
     1784   table. If you need to access more than this you can use extra or 
     1785   `fall back to SQL`_. 
     1786 
     1787   .. _`fall back to SQL`: ../model-api/#falling-back-to--raw-sql 
     1788 
    17471789Spanning multi-valued relationships 
    17481790~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    17491791 
     
    22832325    # Change every Entry so that it belongs to this Blog. 
    22842326    Entry.objects.all().update(blog=b) 
    22852327 
    2286 The ``update()`` method is applied instantly and doesn't return anything 
    2287 (similar to ``delete()``). The only restriction on the ``QuerySet`` that is 
    2288 updated is that it can only access one database table, the model's main 
    2289 table. So don't try to filter based on related fields or anything like that; 
    2290 it won't work. 
     2328The ``update()`` method is applied instantly and returns the number of 
     2329objects that were affected. The only restriction on the ``QuerySet`` 
     2330that is updated is that it can only access one database table, the 
     2331model's main table. So don't try to filter based on related fields or 
     2332anything like that; it won't work. 
    22912333 
    22922334Be aware that the ``update()`` method is converted directly to an SQL 
    22932335statement. It is a bulk operation for direct updates. It doesn't run any 
     
    23002342    for item in my_queryset: 
    23012343        item.save() 
    23022344 
     2345When using ``update()`` you can also refer to other fields in the 
     2346model by using F objects and expressions. Updating the comment 
     2347threshold of all entries for it to be the same as the current number 
     2348of comments could be done like this:: 
    23032349 
     2350    Entry.objects.all().update(comment_threshold=F('num_comments')) 
     2351 
     2352 
    23042353Extra instance methods 
    23052354====================== 
    23062355