mirror of
https://github.com/django/django.git
synced 2025-03-28 18:20:49 +00:00
242 lines
9.1 KiB
Plaintext
242 lines
9.1 KiB
Plaintext
==============
|
||
Custom lookups
|
||
==============
|
||
|
||
.. module:: django.db.models.lookups
|
||
:synopsis: Custom lookups
|
||
|
||
.. currentmodule:: django.db.models
|
||
|
||
Django's ORM works using lookup paths when building query filters and other
|
||
query conditions. For example in the query Book.filter(author__age__lte=30)
|
||
the part "author__age__lte" is the lookup path.
|
||
|
||
The lookup path consist of three different parts. First is the related
|
||
lookups. In the author__age__lte example the part author refers to Book's
|
||
related model Author. Second part of the lookup path is the field. This is
|
||
Author's age field in the example. Finally the lte part is commonly called
|
||
just lookup. Both the related lookups part and the final lookup part can
|
||
contain multiple parts, for example "author__friends__birthdate__year__lte"
|
||
has author, friends as related lookups, birthdate as the field and year, lte
|
||
as final lookup part.
|
||
|
||
This documentation concentrates on writing custom lookups. By writing custom
|
||
lookups it is possible to control how Django interprets the final lookup part.
|
||
|
||
Django will fetch a ``Lookup`` class from the final field using the field's
|
||
method get_lookup(lookup_name). This method is allowed to do these things:
|
||
|
||
1. Return a Lookup class
|
||
2. Raise a FieldError
|
||
3. Return None
|
||
|
||
Returning None is only available during backwards compatibility period.
|
||
The interpretation is to use the old way of lookup hadling inside the ORM.
|
||
|
||
The Lookup class
|
||
~~~~~~~~~~~~~~~~
|
||
|
||
A Lookup operates on two values and produces boolean results. The values
|
||
are called lhs and rhs. The lhs is usually a field reference, but it can be
|
||
anything implementing the query expression API. The rhs is a value to compare
|
||
against.
|
||
|
||
The API is as follows:
|
||
|
||
.. attribute:: lookup_name
|
||
|
||
A string used by Django to distinguish different lookups. For example
|
||
'exact'.
|
||
|
||
.. method:: __init__(lhs, rhs)
|
||
|
||
The lhs is something implementing the query expression API. For example in
|
||
author__age__lte=30 the lhs is a Col instance referencing the age field of
|
||
author model. The rhs is the value to compare against. It can be Python value
|
||
(30 in the example) or SQL reference (produced by using F() or queryset for
|
||
example).
|
||
|
||
.. attribute:: Lookup.lhs
|
||
|
||
The left hand side part of this lookup. You can assume it implements the
|
||
query expression interface.
|
||
|
||
.. attribute:: Lookup.rhs
|
||
|
||
The value to compare against.
|
||
|
||
.. method:: Lookup.process_lhs(qn, connection)
|
||
|
||
Turns the lhs into query string + params.
|
||
|
||
.. method:: Lookup.process_rhs(qn, connection)
|
||
|
||
Turns the rhs into query string + params.
|
||
|
||
.. method:: Lookup.as_sql(qn, connection)
|
||
|
||
This method is used to produce the query string of the Lookup. A typical
|
||
implementation is usually something like::
|
||
|
||
def as_sql(self, qn, connection):
|
||
lhs, params = self.process_lhs(qn, connection)
|
||
rhs, rhs_params = self.process_rhs(qn, connection)
|
||
params = lhs_params.extend(rhs_params)
|
||
return '%s <OPERATOR> %s', (lhs, rhs), params
|
||
|
||
where the <OPERATOR> is some query operator. The qn is a callable that
|
||
can be used to convert strings to quoted variants (that is, colname to
|
||
"colname"). Note that the quotation is *not* safe against SQL injection.
|
||
|
||
In addition the qn implements method compile() which can be used to turn
|
||
anything with as_sql() method to query string. You should always call
|
||
qn.compile(part) instead of part.as_sql(qn, connection) so that 3rd party
|
||
backends have ability to customize the produced query string. More of this
|
||
later on.
|
||
|
||
The connection is the connection the SQL is compiled against.
|
||
|
||
In addition the Lookup class has some private methods - that is, implementing
|
||
just the above mentioned attributes and methods is not enough, instead you
|
||
must subclass Lookup.
|
||
|
||
The Extract class
|
||
~~~~~~~~~~~~~~~~~
|
||
|
||
An Extract is something that converts a value to another value in the query
|
||
string. For example you could have an Extract that procudes modulo 3 of the
|
||
given value. In SQL this is something like "author"."age" % 3.
|
||
|
||
Extracts are used in nested lookups. The Extract class must implement the
|
||
query part interface.
|
||
|
||
Extracts should be written by subclassing django.db.models.Extract.
|
||
|
||
A simple Lookup example
|
||
~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
This is how to write a simple mod3 lookup for IntegerField::
|
||
|
||
from django.db.models import Lookup, IntegerField
|
||
class Mod3(Lookup):
|
||
lookup_name = 'mod3'
|
||
|
||
def as_sql(self, qn, connection):
|
||
lhs_sql, params = self.process_lhs(qn, connection)
|
||
rhs_sql, rhs_params = self.process_rhs(qn, connection)
|
||
params.extend(rhs_params)
|
||
# We need doulbe-escaping for the %%%% operator.
|
||
return '%s %%%% %s' % (lhs_sql, rhs_sql), params
|
||
|
||
IntegerField.register_lookup(Div3)
|
||
|
||
Now all IntegerFields or subclasses of IntegerField will have
|
||
a mod3 lookup. For example you could do Author.objects.filter(age__mod3=2).
|
||
This query would return every author whose age % 3 == 2.
|
||
|
||
A simple nested lookup example
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Here is how to write an Extract and a Lookup for IntegerField. The example
|
||
lookup can be used similarly as the above mod3 lookup, and in addition it
|
||
support nesting lookups::
|
||
|
||
class Mod3Extract(Extract):
|
||
lookup_name = 'mod3'
|
||
|
||
def as_sql(self, qn, connection):
|
||
lhs, lhs_params = qn.compile(self.lhs)
|
||
return '%s %%%% 3' % (lhs,), lhs_params
|
||
|
||
IntegerField.register_lookup(Mod3Extract)
|
||
|
||
Note that if you already added Mod3 for IntegerField in the above
|
||
example, now Mod3Extract will override that lookup.
|
||
|
||
This lookup can be used like Mod3 lookup, but in addition it supports
|
||
nesting, too. The default output type for Extracts is the same type as the
|
||
lhs' output_type. So, the Mod3Extract supports all the same lookups as
|
||
IntegerField. For example Author.objects.filter(age__mod3__in=[1, 2])
|
||
returns all authors for which age % 3 in (1, 2).
|
||
|
||
A more complex nested lookup
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
We will write a Year lookup that extracts year from date field. This
|
||
field will convert the output type of the field - the lhs (or "input")
|
||
field is DateField, but output is of type IntegerField.::
|
||
|
||
from django.db.models import IntegerField, DateField
|
||
from django.db.models.lookups import Extract
|
||
|
||
class YearExtract(Extract):
|
||
lookup_name = 'year'
|
||
|
||
def as_sql(self, qn, connection):
|
||
lhs_sql, params = qn.compile(self.lhs)
|
||
# hmmh - this is internal API...
|
||
return connection.ops.date_extract_sql('year', lhs_sql), params
|
||
|
||
@property
|
||
def output_type(self):
|
||
return IntegerField()
|
||
|
||
DateField.register_lookup(YearExtract)
|
||
|
||
Now you could write Author.objects.filter(birthdate__year=1981). This will
|
||
produce SQL like 'EXTRACT('year' from "author"."birthdate") = 1981'. The
|
||
produces SQL depends on used backend. In addtition you can use any lookup
|
||
defined for IntegerField, even div3 if you added that. So,
|
||
Authos.objects.filter(birthdate__year__div3=2) will return every author
|
||
with birthdate.year % 3 == 2.
|
||
|
||
We could go further and add an optimized implementation for exact lookups::
|
||
|
||
from django.db.models.lookups import Lookup
|
||
|
||
class YearExtractOptimized(YearExtract):
|
||
def get_lookup(self, lookup):
|
||
if lookup == 'exact':
|
||
return YearExact
|
||
return super(YearExtractOptimized, self).get_lookup()
|
||
|
||
class YearExact(Lookup):
|
||
def as_sql(self, qn, connection):
|
||
# We will need to skip the extract part, and instead go
|
||
# directly with the originating field, that is self.lhs.lhs
|
||
lhs_sql, lhs_params = self.process_lhs(qn, connection, self.lhs.lhs)
|
||
rhs_sql, rhs_params = self.process_rhs(qn, connection)
|
||
# Note that we must be careful so that we have params in the
|
||
# same order as we have the parts in the SQL.
|
||
params = []
|
||
params.extend(lhs_params)
|
||
params.extend(rhs_params)
|
||
params.extend(lhs_params)
|
||
params.extend(rhs_params)
|
||
# We use PostgreSQL specific SQL here. Note that we must do the
|
||
# conversions in SQL instead of in Python to support F() references.
|
||
return ("%(lhs)s >= (%(rhs)s || '-01-01')::date "
|
||
"AND %(lhs)s <= (%(rhs)s || '-12-31')::date" %
|
||
{'lhs': lhs_sql, 'rhs': rhs_sql}, params)
|
||
|
||
Note that we used PostgreSQL specific SQL above. What if we want to support
|
||
MySQL, too? This can be done by registering a different compiling implementation
|
||
for MySQL::
|
||
|
||
from django.db.backends.utils import add_implementation
|
||
@add_implementation(YearExact, 'mysql')
|
||
def mysql_year_exact(node, qn, connection):
|
||
lhs_sql, lhs_params = node.process_lhs(qn, connection, node.lhs.lhs)
|
||
rhs_sql, rhs_params = node.process_rhs(qn, connection)
|
||
params = []
|
||
params.extend(lhs_params)
|
||
params.extend(rhs_params)
|
||
params.extend(lhs_params)
|
||
params.extend(rhs_params)
|
||
return ("%(lhs)s >= str_to_date(concat(%(rhs)s, '-01-01'), '%%%%Y-%%%%m-%%%%d') "
|
||
"AND %(lhs)s <= str_to_date(concat(%(rhs)s, '-12-31'), '%%%%Y-%%%%m-%%%%d')" %
|
||
{'lhs': lhs_sql, 'rhs': rhs_sql}, params)
|
||
|
||
Now, on MySQL instead of calling as_sql() of the YearExact Django will use the
|
||
above compile implementation.
|