Django

Code

Changeset 1884

Show
Ignore:
Timestamp:
01/09/06 05:01:38 (3 years ago)
Author:
russellm
Message:

magic-removal: Fixed #1133 -- Added ability to use Q objects as args
in DB lookup queries.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/magic-removal/django/db/models/manager.py

    r1835 r1884  
    66from django.dispatch import dispatcher 
    77from django.db.models import signals 
     8from django.utils.datastructures import SortedDict 
    89 
    910# Size of each "chunk" for get_iterator calls. 
     
    4849                klass._default_manager = self 
    4950 
    50     def _get_sql_clause(self, **kwargs): 
     51    def _get_sql_clause(self, *args, **kwargs): 
    5152        def quote_only_if_word(word): 
    5253            if ' ' in word: 
     
    6061        select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields] 
    6162        tables = (kwargs.get('tables') and [quote_only_if_word(t) for t in kwargs['tables']] or []) 
     63        joins = SortedDict() 
    6264        where = kwargs.get('where') and kwargs['where'][:] or [] 
    6365        params = kwargs.get('params') and kwargs['params'][:] or [] 
    6466 
     67        # Convert all the args into SQL. 
     68        table_count = 0 
     69        for arg in args: 
     70            # check that the provided argument is a Query (i.e., it has a get_sql method) 
     71            if not hasattr(arg, 'get_sql'): 
     72                raise TypeError, "'%s' is not a valid query argument" % str(arg) 
     73 
     74            tables2, joins2, where2, params2 = arg.get_sql(opts) 
     75            tables.extend(tables2) 
     76            joins.update(joins2) 
     77            where.extend(where2) 
     78            params.extend(params2) 
     79 
     80 
    6581        # Convert the kwargs into SQL. 
    66         tables2, joins, where2, params2 = parse_lookup(kwargs.items(), opts) 
     82        tables2, joins2, where2, params2 = parse_lookup(kwargs.items(), opts) 
    6783        tables.extend(tables2) 
     84        joins.update(joins2) 
    6885        where.extend(where2) 
    6986        params.extend(params2) 
     
    130147        return select, " ".join(sql), params 
    131148 
    132     def get_iterator(self, **kwargs): 
     149    def get_iterator(self, *args, **kwargs): 
    133150        # kwargs['select'] is a dictionary, and dictionaries' key order is 
    134151        # undefined, so we convert it to a list of tuples internally. 
     
    136153 
    137154        cursor = connection.cursor() 
    138         select, sql, params = self._get_sql_clause(**kwargs) 
     155        select, sql, params = self._get_sql_clause(*args, **kwargs) 
    139156        cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params) 
    140157        fill_cache = kwargs.get('select_related') 
     
    153170                yield obj 
    154171 
    155     def get_list(self, **kwargs): 
    156         return list(self.get_iterator(**kwargs)) 
    157  
    158     def get_count(self, **kwargs): 
     172    def get_list(self, *args, **kwargs): 
     173        return list(self.get_iterator(*args, **kwargs)) 
     174 
     175    def get_count(self, *args, **kwargs): 
    159176        kwargs['order_by'] = [] 
    160177        kwargs['offset'] = None 
    161178        kwargs['limit'] = None 
    162179        kwargs['select_related'] = False 
    163         _, sql, params = self._get_sql_clause(**kwargs) 
     180        _, sql, params = self._get_sql_clause(*args, **kwargs) 
    164181        cursor = connection.cursor() 
    165182        cursor.execute("SELECT COUNT(*)" + sql, params) 
    166183        return cursor.fetchone()[0] 
    167184 
    168     def get_object(self, **kwargs): 
    169         obj_list = self.get_list(**kwargs) 
     185    def get_object(self, *args, **kwargs): 
     186        obj_list = self.get_list(*args, **kwargs) 
    170187        if len(obj_list) < 1: 
    171188            raise self.klass.DoesNotExist, "%s does not exist for %s" % (self.klass._meta.object_name, kwargs) 
     
    174191 
    175192    def get_in_bulk(self, *args, **kwargs): 
    176         id_list = args and args[0] or kwargs.get('id_list', []) 
    177         assert id_list != [], "get_in_bulk() cannot be passed an empty list." 
     193        # Separate any list arguments: the first list will be used as the id list; subsequent 
     194        # lists will be ignored. 
     195        id_args = filter(lambda arg: isinstance(arg, list), args) 
     196        # Separate any non-list arguments: these are assumed to be query arguments 
     197        sql_args = filter(lambda arg: not isinstance(arg, list), args) 
     198 
     199        id_list = id_args and id_args[0] or kwargs.get('id_list', []) 
     200        assert id_list != [], "get_in_bulk() cannot be passed an empty ID list."         
    178201        kwargs['where'] = ["%s.%s IN (%s)" % (backend.quote_name(self.klass._meta.db_table), backend.quote_name(self.klass._meta.pk.column), ",".join(['%s'] * len(id_list)))] 
    179202        kwargs['params'] = id_list 
    180         obj_list = self.get_list(**kwargs) 
     203        obj_list = self.get_list(*sql_args, **kwargs) 
    181204        return dict([(getattr(o, self.klass._meta.pk.attname), o) for o in obj_list]) 
    182205 
    183     def get_values_iterator(self, **kwargs): 
     206    def get_values_iterator(self, *args, **kwargs): 
    184207        # select_related and select aren't supported in get_values(). 
    185208        kwargs['select_related'] = False 
     
    193216 
    194217        cursor = connection.cursor() 
    195         _, sql, params = self._get_sql_clause(**kwargs) 
     218        _, sql, params = self._get_sql_clause(*args, **kwargs) 
    196219        select = ['%s.%s' % (backend.quote_name(self.klass._meta.db_table), backend.quote_name(f)) for f in fields] 
    197220        cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params) 
     
    203226                yield dict(zip(fields, row)) 
    204227 
    205     def get_values(self, **kwargs): 
    206         return list(self.get_values_iterator(**kwargs)) 
    207  
    208     def __get_latest(self, **kwargs): 
     228    def get_values(self, *args, **kwargs): 
     229        return list(self.get_values_iterator(*args, **kwargs)) 
     230 
     231    def __get_latest(self, *args, **kwargs): 
    209232        kwargs['order_by'] = ('-' + self.klass._meta.get_latest_by,) 
    210233        kwargs['limit'] = 1 
    211         return self.get_object(**kwargs) 
     234        return self.get_object(*args, **kwargs) 
    212235 
    213236    def __get_date_list(self, field, *args, **kwargs): 
     237        # Separate any string arguments: the first will be used as the kind 
     238        kind_args = filter(lambda arg: isinstance(arg, str), args) 
     239        # Separate any non-list arguments: these are assumed to be query arguments 
     240        sql_args = filter(lambda arg: not isinstance(arg, str), args) 
     241         
    214242        from django.db.backends.util import typecast_timestamp 
    215         kind = args and args[0] or kwargs['kind'] 
     243        kind = kind_args and kind_args[0] or kwargs.get(['kind'],"") 
    216244        assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'." 
    217245        order = 'ASC' 
     
    224252            kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \ 
    225253                (backend.quote_name(self.klass._meta.db_table), backend.quote_name(field.column))) 
    226         select, sql, params = self._get_sql_clause(**kwargs) 
     254        select, sql, params = self._get_sql_clause(*sql_args, **kwargs) 
    227255        sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ 
    228256            (backend.get_date_trunc_sql(kind, '%s.%s' % (backend.quote_name(self.klass._meta.db_table), 
  • django/branches/magic-removal/django/db/models/query.py

    r1856 r1884  
    199199            pass 
    200200        elif kwarg == 'complex': 
     201            if not hasattr(value, 'get_sql'): 
     202                raise TypeError, "'%s' is not a valid query argument" % str(arg) 
    201203            tables2, joins2, where2, params2 = value.get_sql(opts) 
    202204            tables.extend(tables2) 
  • django/branches/magic-removal/docs/db-api.txt

    r1873 r1884  
    225225**New in Django development version.** 
    226226 
    227 By default, multiple lookups are "AND"ed together. If you'd like to use ``OR`` 
    228 statements in your queries, use the ``complex`` lookup type. 
    229  
    230 ``complex`` takes an expression of clauses, each of which is an instance of 
    231 ``django.core.meta.Q``. ``Q`` takes an arbitrary number of keyword arguments in 
    232 the standard Django lookup format. And you can use Python's "and" (``&``) and 
    233 "or" (``|``) operators to combine ``Q`` instances. For example:: 
    234  
    235     from django.core.meta import Q 
    236     polls.get_object(complex=(Q(question__startswith='Who') | Q(question__startswith='What'))) 
    237  
    238 The ``|`` symbol signifies an "OR", so this (roughly) translates into:: 
    239  
    240     SELECT * FROM polls 
    241     WHERE question LIKE 'Who%' OR question LIKE 'What%'; 
    242  
    243 You can use ``&`` and ``|`` operators together, and use parenthetical grouping. 
    244 Example:: 
    245  
    246     polls.get_object(complex=(Q(question__startswith='Who') & (Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6)))) 
    247  
    248 This roughly translates into:: 
    249  
    250     SELECT * FROM polls 
    251     WHERE question LIKE 'Who%' 
    252         AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06'); 
     227By default, keyword argument queries are "AND"ed together. If you have more complex query  
     228requirements (for example, you need to include an ``OR`` statement in your query), you need  
     229to use ``Q`` objects. 
     230 
     231A ``Q`` object is an instance of ``django.core.meta.Q``, used to encapsulate a collection of  
     232keyword arguments. These keyword arguments are specified in the same way as keyword arguments to 
     233the basic lookup functions like get_object() and get_list(). For example:: 
     234 
     235    Q(question__startswith='What') 
     236 
     237``Q`` objects can be combined using the ``&`` and ``|`` operators. When an operator is used on two 
     238``Q`` objects, it yields a new ``Q`` object. For example the statement:: 
     239 
     240    Q(question__startswith='Who') | Q(question__startswith='What') 
     241 
     242... yields a single ``Q`` object that represents the "OR" of two "question__startswith" queries, equivalent to the SQL WHERE clause:: 
     243 
     244    ... WHERE question LIKE 'Who%' OR question LIKE 'What%' 
     245 
     246You can compose statements of arbitrary complexity by combining ``Q`` objects with the ``&`` and ``|`` operators. Parenthetical grouping can also be used.  
     247 
     248One or more ``Q`` objects can then provided as arguments to the lookup functions. If multiple  
     249``Q`` object arguments are provided to a lookup function, they will be "AND"ed together.  
     250For example:: 
     251 
     252    polls.get_object( 
     253        Q(question__startswith='Who'),  
     254        Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6)) 
     255    ) 
     256 
     257... roughly translates into the SQL:: 
     258 
     259    SELECT * from polls WHERE question LIKE 'Who%' 
     260        AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06') 
     261 
     262If necessary, lookup functions can mix the use of ``Q`` objects and keyword arguments. All arguments 
     263provided to a lookup function (be they keyword argument or ``Q`` object) are "AND"ed together.  
     264However, if a ``Q`` object is provided, it must precede the definition of any keyword arguments.  
     265For example:: 
     266 
     267    polls.get_object( 
     268        Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6)), 
     269        question__startswith='Who') 
     270 
     271... would be a valid query, equivalent to the previous example; but:: 
     272 
     273    # INVALID QUERY 
     274    polls.get_object( 
     275        question__startswith='Who',  
     276        Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6))) 
     277 
     278... would not be valid.  
     279 
     280A ``Q`` objects can also be provided to the ``complex`` keyword argument. For example:: 
     281 
     282    polls.get_object( 
     283        complex=Q(question__startswith='Who') &  
     284            (Q(pub_date__exact=date(2005, 5, 2)) |  
     285             Q(pub_date__exact=date(2005, 5, 6)) 
     286        ) 
     287    ) 
    253288 
    254289See the `OR lookups examples page`_ for more examples. 
  • django/branches/magic-removal/tests/modeltests/lookup/models.py

    r1835 r1884  
    6969Traceback (most recent call last): 
    7070    ... 
    71 AssertionError: get_in_bulk() cannot be passed an empty list. 
     71AssertionError: get_in_bulk() cannot be passed an empty ID list. 
    7272 
    7373# get_values() is just like get_list(), except it returns a list of 
  • django/branches/magic-removal/tests/modeltests/or_lookups/models.py

    r1754 r1884  
    44To perform an OR lookup, or a lookup that combines ANDs and ORs, use the 
    55``complex`` keyword argument, and pass it an expression of clauses using the 
    6 variable ``django.db.models.Q``
     6variable ``django.db.models.Q`` (or any object with a get_sql method)
    77""" 
    88 
     
    5555[Hello, Goodbye, Hello and goodbye] 
    5656 
     57# Queries can use Q objects as args 
     58>>> Article.objects.get_list(Q(headline__startswith='Hello')) 
     59[Hello, Hello and goodbye] 
     60 
     61# Q arg objects are ANDed  
     62>>> Article.objects.get_list(Q(headline__startswith='Hello'), Q(headline__contains='bye')) 
     63[Hello and goodbye] 
     64 
     65# Q arg AND order is irrelevant 
     66>>> Article.objects.get_list(Q(headline__contains='bye'), headline__startswith='Hello') 
     67[Hello and goodbye] 
     68 
     69# QOrs are ok, as they ultimately resolve to a Q  
     70>>> Article.objects.get_list(Q(headline__contains='Hello') | Q(headline__contains='bye')) 
     71[Hello, Goodbye, Hello and goodbye] 
     72 
     73# Try some arg queries with operations other than get_list 
     74>>> Article.objects.get_object(Q(headline__startswith='Hello'), Q(headline__contains='bye')) 
     75Hello and goodbye 
     76 
     77>>> Article.objects.get_count(Q(headline__startswith='Hello') | Q(headline__contains='bye')) 
     783 
     79 
     80>>> Article.objects.get_values(Q(headline__startswith='Hello'), Q(headline__contains='bye')) 
     81[{'headline': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}] 
     82 
     83>>> Article.objects.get_in_bulk([1,2], Q(headline__startswith='Hello')) 
     84{1: Hello} 
     85 
    5786"""