Django

Code

Changeset 1508

Show
Ignore:
Timestamp:
11/30/05 00:14:05 (3 years ago)
Author:
adrian
Message:

Fixed #251 -- Added OR support to queries, via the new 'complex' DB API keyword argument. Updated docs and added unit tests. Also removed old, undocumented '_or' parameter. Thanks, Hugo.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/admin/views/main.py

    r1507 r1508  
    217217        lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,) 
    218218        if lookup_opts.admin.search_fields and query: 
    219             or_queries = [] 
     219            complex_queries = [] 
    220220            for bit in query.split(): 
    221                 or_query = [] 
     221                or_queries = [] 
    222222                for field_name in lookup_opts.admin.search_fields: 
    223                     or_query.append(('%s__icontains' % field_name, bit)) 
    224                 or_queries.append(or_query) 
    225             lookup_params['_or'] = or_queries 
    226  
     223                    or_queries.append(meta.Q(**{'%s__icontains' % field_name: bit})) 
     224                complex_queries.append(reduce(operator.or_, or_queries)) 
     225            lookup_params['complex'] = reduce(operator.and_, complex_queries) 
    227226        if opts.one_to_one_field: 
    228227            lookup_params.update(opts.one_to_one_field.rel.limit_choices_to) 
    229228        self.lookup_params = lookup_params 
    230  
    231229 
    232230def change_list(request, app_label, module_name): 
  • django/trunk/django/core/meta/__init__.py

    r1504 r1508  
    282282            rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name) 
    283283        return rel_obj_name 
     284 
     285class QBase: 
     286    "Base class for QAnd and QOr" 
     287    def __init__(self, *args): 
     288        self.args = args 
     289 
     290    def __repr__(self): 
     291        return '(%s)' % self.operator.join([repr(el) for el in self.args]) 
     292 
     293    def get_sql(self, opts, table_count): 
     294        tables, join_where, where, params = [], [], [], [] 
     295        for val in self.args: 
     296            tables2, join_where2, where2, params2, table_count = val.get_sql(opts, table_count) 
     297            tables.extend(tables2) 
     298            join_where.extend(join_where2) 
     299            where.extend(where2) 
     300            params.extend(params2) 
     301        return tables, join_where, ['(%s)' % self.operator.join(where)], params, table_count 
     302 
     303class QAnd(QBase): 
     304    "Encapsulates a combined query that uses 'AND'." 
     305    operator = ' AND ' 
     306    def __or__(self, other): 
     307        if isinstance(other, (QAnd, QOr, Q)): 
     308            return QOr(self, other) 
     309        else: 
     310            raise TypeError, other 
     311 
     312    def __and__(self, other): 
     313        if isinstance(other, QAnd): 
     314            return QAnd(*(self.args+other.args)) 
     315        elif isinstance(other, (Q, QOr)): 
     316            return QAnd(*(self.args+(other,))) 
     317        else: 
     318            raise TypeError, other 
     319 
     320class QOr(QBase): 
     321    "Encapsulates a combined query that uses 'OR'." 
     322    operator = ' OR ' 
     323    def __and__(self, other): 
     324        if isinstance(other, (QAnd, QOr, Q)): 
     325            return QAnd(self, other) 
     326        else: 
     327            raise TypeError, other 
     328 
     329    def __or__(self, other): 
     330        if isinstance(other, QOr): 
     331            return QOr(*(self.args+other.args)) 
     332        elif isinstance(other, (Q, QAnd)): 
     333            return QOr(*(self.args+(other,))) 
     334        else: 
     335            raise TypeError, other 
     336 
     337class Q: 
     338    "Encapsulates queries for the 'complex' parameter to Django API functions." 
     339    def __init__(self, **kwargs): 
     340        self.kwargs = kwargs 
     341 
     342    def __repr__(self): 
     343        return 'Q%r' % self.kwargs 
     344 
     345    def __and__(self, other): 
     346        if isinstance(other, (Q, QAnd, QOr)): 
     347            return QAnd(self, other) 
     348        else: 
     349            raise TypeError, other 
     350 
     351    def __or__(self, other): 
     352        if isinstance(other, (Q, QAnd, QOr)): 
     353            return QOr(self, other) 
     354        else: 
     355            raise TypeError, other 
     356 
     357    def get_sql(self, opts, table_count): 
     358        return _parse_lookup(self.kwargs.items(), opts, table_count) 
    284359 
    285360class Options: 
     
    13901465            continue 
    13911466        if kwarg_value is None: 
     1467            continue 
     1468        if kwarg == 'complex': 
     1469            tables2, join_where2, where2, params2, table_count = kwarg_value.get_sql(opts, table_count) 
     1470            tables.extend(tables2) 
     1471            join_where.extend(join_where2) 
     1472            where.extend(where2) 
     1473            params.extend(params2) 
    13921474            continue 
    13931475        if kwarg == '_or': 
  • django/trunk/docs/db-api.txt

    r1352 r1508  
    220220.. _`Keyword Arguments`: http://docs.python.org/tut/node6.html#SECTION006720000000000000000 
    221221 
     222OR lookups 
     223---------- 
     224 
     225**New in Django development version.** 
     226 
     227By default, multiple lookups are "AND"ed together. If you'd like to use ``OR`` 
     228statements 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 
     232the 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 
     238The ``|`` symbol signifies an "OR", so this (roughly) translates into:: 
     239 
     240    SELECT * FROM polls 
     241    WHERE question LIKE 'Who%' OR question LIKE 'What%'; 
     242 
     243You can use ``&`` and ``|`` operators together, and use parenthetical grouping. 
     244Example:: 
     245 
     246    polls.get_object(complex=(Q(question__startswith='Who') & (Q(pub_date__exact=date(2005, 5, 2)) | pub_date__exact=date(2005, 5, 6))) 
     247 
     248This 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'); 
     253 
     254See the `OR lookups examples page`_ for more examples. 
     255 
     256.. _OR lookups examples page: http://www.djangoproject.com/documentation/models/or_lookups/ 
     257 
    222258Ordering 
    223259======== 
  • django/trunk/tests/testapp/models/__init__.py

    r1224 r1508  
    22           'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one', 
    33           'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk', 
    4            'subclassing', 'many_to_one_null', 'custom_columns', 'reserved_names'] 
     4           'subclassing', 'many_to_one_null', 'custom_columns', 'reserved_names', 
     5           'or_lookups']