mirror of https://github.com/django/django.git
A new try for docs
This commit is contained in:
parent
33aa18a6e3
commit
f9cc039007
|
@ -0,0 +1,257 @@
|
||||||
|
==============
|
||||||
|
Custom lookups
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. module:: django.db.models.lookups
|
||||||
|
:synopsis: Custom lookups
|
||||||
|
|
||||||
|
.. currentmodule:: django.db.models
|
||||||
|
|
||||||
|
By default Django offers a wide variety of different lookups for filtering
|
||||||
|
(for example, `exact` and `icontains`). This documentation explains how to
|
||||||
|
write custom lookups and how to alter the working of existing lookups. In
|
||||||
|
addition how to transform field values is explained. fFor example how to
|
||||||
|
extract the year from a DateField. By writing a custom `YearExtract`
|
||||||
|
transformer it is possible to filter on the transformed value, for example::
|
||||||
|
|
||||||
|
Author.objects.filter(birthdate__year__lte=1981)
|
||||||
|
|
||||||
|
Currently transformers are only available in filtering. So, it is not possible
|
||||||
|
to use it in other parts of the ORM, for example this will not work::
|
||||||
|
|
||||||
|
Author.objects.values_list('birthdate__year')
|
||||||
|
|
||||||
|
A simple Lookup example
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Lets start with a simple custom lookup. We will write a custom lookup `ne`
|
||||||
|
which works opposite to `exact`. A `Author.objects.filter(name__ne='Jack')`
|
||||||
|
will translate to::
|
||||||
|
|
||||||
|
"author"."name" <> 'Jack'
|
||||||
|
|
||||||
|
A custom lookup will need an implementation and Django needs to be told
|
||||||
|
the existence of the lookup. The implementation for this lookup will be
|
||||||
|
simple to write::
|
||||||
|
|
||||||
|
from django.db.models import Lookup
|
||||||
|
|
||||||
|
class NotEqual(Lookup):
|
||||||
|
lookup_name = 'ne'
|
||||||
|
|
||||||
|
def as_sql(self, qn, connection):
|
||||||
|
lhs, lhs_params = self.process_lhs(qn, connection)
|
||||||
|
rhs, rhs_params = self.process_rhs(qn, connection)
|
||||||
|
params = lhs_params + rhs_params
|
||||||
|
return '%s <> %s' % (lhs, rhs), params
|
||||||
|
|
||||||
|
To register the `NotEqual` lookup we will just need to call register_lookup
|
||||||
|
on the field class we want the lookup to be available::
|
||||||
|
|
||||||
|
from django.db.models.fields import Field
|
||||||
|
Field.register_lookup(NotEqual)
|
||||||
|
|
||||||
|
Now Field and all its subclasses have a NotEqual lookup.
|
||||||
|
|
||||||
|
The first notable thing about `NotEqual` is the lookup_name. This name must
|
||||||
|
be supplied, and it is used by Django in the register_lookup() call so that
|
||||||
|
Django knows to associate `ne` to the NotEqual implementation.
|
||||||
|
`
|
||||||
|
An Lookup works against two values, lhs and rhs. The abbreviations stand for
|
||||||
|
left-hand side and right-hand side. The lhs is usually a field reference,
|
||||||
|
but it can be anything implementing the query expression API. The
|
||||||
|
rhs is the value given by the user. In the example `name__ne=Jack`, the
|
||||||
|
lhs is reference to Author's name field and Jack is the value.
|
||||||
|
|
||||||
|
The lhs and rhs are turned into values that are possible to use in SQL.
|
||||||
|
In the example above lhs is turned into "author"."name", [], and rhs is
|
||||||
|
turned into "%s", ['Jack']. The lhs is just raw string without parameters
|
||||||
|
but the rhs is turned into a query parameter 'Jack'.
|
||||||
|
|
||||||
|
Finally we combine the lhs and rhs by adding ` <> ` in between of them,
|
||||||
|
and supply all the parameters for the query.
|
||||||
|
|
||||||
|
A Lookup needs to implement a limited part of query expression API. See
|
||||||
|
the query expression API for details.
|
||||||
|
|
||||||
|
A simple transformer example
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
We will next write a simple transformer. The transformer will be called
|
||||||
|
`YearExtract`. It can be used to extract the year part from `DateField`.
|
||||||
|
|
||||||
|
Lets start by writing the implementation::
|
||||||
|
|
||||||
|
from django.db.models import Extract
|
||||||
|
|
||||||
|
class YearExtract(Extract):
|
||||||
|
lookup_name = 'year'
|
||||||
|
output_type = IntegerField()
|
||||||
|
|
||||||
|
def as_sql(self, qn, connection):
|
||||||
|
lhs, params = qn.compile(self.lhs)
|
||||||
|
return "EXTRACT(YEAR FROM %s)" % lhs, params
|
||||||
|
|
||||||
|
Next, lets register it for `DateField`::
|
||||||
|
|
||||||
|
from django.db.models import DateField
|
||||||
|
DateField.register_lookup(YearExtract)
|
||||||
|
|
||||||
|
Now any DateField in your project will have `year` transformer. For example
|
||||||
|
the following query::
|
||||||
|
|
||||||
|
Author.objects.filter(birthdate__year__lte=1981)
|
||||||
|
|
||||||
|
would translate to the following query on PostgreSQL::
|
||||||
|
|
||||||
|
SELECT ...
|
||||||
|
FROM "author"
|
||||||
|
WHERE EXTRACT(YEAR FROM "author"."birthdate") <= 1981
|
||||||
|
|
||||||
|
An YearExtract class works only against self.lhs. Usually the lhs is
|
||||||
|
transformed in some way. Further lookups and extracts work against the
|
||||||
|
transformed value.
|
||||||
|
|
||||||
|
Note the definition of output_type in the `YearExtract`. The output_type is
|
||||||
|
a field instance. It informs Django that the Extract class transformed the
|
||||||
|
type of the value to an int. This is currently used only to check which
|
||||||
|
lookups the extract has.
|
||||||
|
|
||||||
|
The used SQL in this example works on most databases. Check you database
|
||||||
|
vendor's documentation to see if EXTRACT(year from date) is supported.
|
||||||
|
|
||||||
|
Writing an efficient year__exact lookup
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When using the above written `year` lookup, the SQL produced will not use
|
||||||
|
indexes efficiently. We will fix that by writing a custom `exact` lookup
|
||||||
|
for YearExtract. For example if the user filters on
|
||||||
|
`birthdate__year__exact=1981`, then we want to produce the following SQL::
|
||||||
|
|
||||||
|
birthdate >= to_date('1981-01-01') AND birthdate <= to_date('1981-12-31')
|
||||||
|
|
||||||
|
The implementation is::
|
||||||
|
|
||||||
|
from django.db.models import Lookup
|
||||||
|
|
||||||
|
class YearExact(Lookup):
|
||||||
|
lookup_name = 'exact'
|
||||||
|
|
||||||
|
def as_sql(self, qn, connection):
|
||||||
|
lhs, lhs_params = qn.compile(self.lhs.lhs)
|
||||||
|
rhs, rhs_params = self.process_rhs(qn, connection)
|
||||||
|
params = lhs_params + rhs_params + lhs_params + rhs_params
|
||||||
|
return '%s >= to_date(%s || '-01-01') AND %s <= to_date(%s || '-12-31') % (lhs, rhs, lhs, rhs), params
|
||||||
|
|
||||||
|
YearExtract.register_lookup(YearExact)
|
||||||
|
|
||||||
|
There are a couple of notable things going on. First, `YearExact` isn't
|
||||||
|
calling process_lhs(). Instead it skips and compiles directly the lhs used by
|
||||||
|
self.lhs. The reason this is done is to skip `YearExtract` from adding the
|
||||||
|
EXTRACT clause to the query. Referring directly to self.lhs.lhs is safe as
|
||||||
|
`YearExact` can be accessed only from `year__exact` lookup, that is the lhs
|
||||||
|
is always `YearExtract`.
|
||||||
|
|
||||||
|
Next, as both the lhs and rhs are used multiple times in the query the params
|
||||||
|
need to contain lhs_params and rhs_params multiple times.
|
||||||
|
|
||||||
|
The final query does string manipulation directly in the database. The reason
|
||||||
|
for doing this is that if the self.rhs is something else than a plain integer
|
||||||
|
value (for exampel a `F()` reference) we can't do the transformations in
|
||||||
|
Python.
|
||||||
|
|
||||||
|
Writing alternative implemenatations for existing lookups
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Sometimes different database vendors require different SQL for the same
|
||||||
|
operation. For this example we will rewrite a custom implementation for
|
||||||
|
MySQL for the NotEqual operator. Instead of `<>` we will be using `!=`
|
||||||
|
operator.
|
||||||
|
|
||||||
|
There are two ways to do this. The first is to write a subclass with a
|
||||||
|
as_mysql() method and registering the subclass over the original class::
|
||||||
|
|
||||||
|
class MySQLNotEqual(NotEqual):
|
||||||
|
def as_mysql(self, qn, connection):
|
||||||
|
lhs, lhs_params = self.process_lhs(qn, connection)
|
||||||
|
rhs, rhs_params = self.process_rhs(qn, connection)
|
||||||
|
params = lhs_params + rhs_params
|
||||||
|
return '%s != %s' % (lhs, rhs), params
|
||||||
|
Field.register_lookup(MySQLNotExact)
|
||||||
|
|
||||||
|
The alternate is to monkey-patch the existing class in place::
|
||||||
|
def as_mysql(self, qn, connection):
|
||||||
|
lhs, lhs_params = self.process_lhs(qn, connection)
|
||||||
|
rhs, rhs_params = self.process_rhs(qn, connection)
|
||||||
|
params = lhs_params + rhs_params
|
||||||
|
return '%s != %s' % (lhs, rhs), params
|
||||||
|
NotEqual.as_mysql = as_mysql
|
||||||
|
|
||||||
|
The subclass way allows one to override methods of the lookup if needed. The
|
||||||
|
monkey-patch way allows writing different implementations for the same class
|
||||||
|
in different locations of the project.
|
||||||
|
|
||||||
|
The way Django knows to call as_mysql() instead of as_sql() is as follows.
|
||||||
|
When qn.compile(notequal_instance) is called, Django first checks if there
|
||||||
|
is a method named 'as_%s' % connection.vendor. If that method doesn't exist,
|
||||||
|
the as_sql() will be called.
|
||||||
|
|
||||||
|
The vendor names for Django's in-built backends are 'sqlite', 'postgresql',
|
||||||
|
'oracle' and 'mysql'.
|
||||||
|
|
||||||
|
The Lookup API
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
An lookup has attributes lhs and rhs. The lhs is something implementing the
|
||||||
|
query expression API and the rhs is either a plain value, or something that
|
||||||
|
needs to be compiled into SQL. Examples of SQL-compiled values include `F()`
|
||||||
|
references and usage of `QuerySets` as value.
|
||||||
|
|
||||||
|
A lookup needs to define lookup_name as a class level attribute. This is used
|
||||||
|
when registering lookups.
|
||||||
|
|
||||||
|
A lookup has three public methods. The as_sql(qn, connection) method needs
|
||||||
|
to produce a query string and parameters used by the query string. The qn has
|
||||||
|
a method compile() which can be used to compile self.lhs. However usually it
|
||||||
|
is better to call self.process_lhs(qn, connection) instead, which returns
|
||||||
|
query string and parameters for the lhs. Similary process_rhs(qn, connection)
|
||||||
|
returns query string and parameters for the rhs.
|
||||||
|
|
||||||
|
The Query Expression API
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A lookup can assume that the lhs responds to the query expression API.
|
||||||
|
Currently direct field references, aggregates and `Extract` instances respond
|
||||||
|
to this API.
|
||||||
|
|
||||||
|
.. method:: as_sql(qn, connection)
|
||||||
|
|
||||||
|
Responsible for producing the query string and parameters for the expression.
|
||||||
|
The qn has a compile() method that can be used to compile other expressions.
|
||||||
|
The connection is the connection used to execute the query. The
|
||||||
|
connection.vendor attribute can be used to return different query strings
|
||||||
|
for different backends.
|
||||||
|
|
||||||
|
Calling expression.as_sql() directly is usually an error - instead
|
||||||
|
qn.compile(expression) should be used. The qn.compile() method will take
|
||||||
|
care of calling vendor-specific methods of the expression.
|
||||||
|
|
||||||
|
.. method:: as_vendorname(qn, connection)
|
||||||
|
|
||||||
|
Works like as_sql() method. When an expression is compiled by qn.compile()
|
||||||
|
Django will first try to call as_vendorname(), where vendorname is the vendor
|
||||||
|
name of the backend used for executing the query. The vendorname is one of
|
||||||
|
'postgresql', 'oracle', 'sqlite' or 'mysql' for Django's inbuilt backends.
|
||||||
|
|
||||||
|
.. method:: get_lookup(lookup_name)::
|
||||||
|
|
||||||
|
The get_lookup() method is used to fetch lookups. By default the lookup
|
||||||
|
is fetched from the expression's output type, but it is possible to override
|
||||||
|
this method to alter that behaviour.
|
||||||
|
|
||||||
|
.. attribute:: output_type
|
||||||
|
|
||||||
|
The output_type attribute is used by the get_lookup() method to check for
|
||||||
|
lookups. The output_type should be a field instance.
|
||||||
|
|
||||||
|
Note that this documentation lists only the public methods of the API.
|
|
@ -1,241 +0,0 @@
|
||||||
==============
|
|
||||||
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.
|
|
Loading…
Reference in New Issue