summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/howto/custom-model-fields.txt6
-rw-r--r--docs/index.txt3
-rw-r--r--docs/ref/models/custom-lookups.txt336
-rw-r--r--docs/ref/models/fields.txt7
-rw-r--r--docs/ref/models/index.txt1
-rw-r--r--docs/ref/models/querysets.txt3
-rw-r--r--docs/releases/1.7.txt21
7 files changed, 376 insertions, 1 deletions
diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt
index b7bcefad53..3d6eeadb61 100644
--- a/docs/howto/custom-model-fields.txt
+++ b/docs/howto/custom-model-fields.txt
@@ -662,6 +662,12 @@ Django filter lookups: ``exact``, ``iexact``, ``contains``, ``icontains``,
``endswith``, ``iendswith``, ``range``, ``year``, ``month``, ``day``,
``isnull``, ``search``, ``regex``, and ``iregex``.
+.. versionadded:: 1.7
+
+ If you are using :doc:`Custom lookups </ref/models/custom-lookups>` the
+ ``lookup_type`` can be any ``lookup_name`` used by the project's custom
+ lookups.
+
Your method must be prepared to handle all of these ``lookup_type`` values and
should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a
list when you were expecting an object, for example) or a ``TypeError`` if
diff --git a/docs/index.txt b/docs/index.txt
index f37a597276..1856beb65e 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -81,7 +81,8 @@ manipulating the data of your Web application. Learn more about it below:
:doc:`Transactions <topics/db/transactions>` |
:doc:`Aggregation <topics/db/aggregation>` |
:doc:`Custom fields <howto/custom-model-fields>` |
- :doc:`Multiple databases <topics/db/multi-db>`
+ :doc:`Multiple databases <topics/db/multi-db>` |
+ :doc:`Custom lookups <ref/models/custom-lookups>`
* **Other:**
:doc:`Supported databases <ref/databases>` |
diff --git a/docs/ref/models/custom-lookups.txt b/docs/ref/models/custom-lookups.txt
new file mode 100644
index 0000000000..7798e92d30
--- /dev/null
+++ b/docs/ref/models/custom-lookups.txt
@@ -0,0 +1,336 @@
+==============
+Custom lookups
+==============
+
+.. versionadded:: 1.7
+
+.. module:: django.db.models.lookups
+ :synopsis: Custom lookups
+
+.. currentmodule:: django.db.models
+
+By default Django offers a wide variety of :ref:`built-in lookups
+<field-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.
+
+A simple Lookup example
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's start with a simple custom lookup. We will write a custom lookup ``ne``
+which works opposite to ``exact``. ``Author.objects.filter(name__ne='Jack')``
+will translate to the SQL::
+
+ "author"."name" <> 'Jack'
+
+This SQL is backend independent, so we don't need to worry about different
+databases.
+
+There are two steps to making this work. Firstly we need to implement the
+lookup, then we need to tell Django about it. The implementation is quite
+straightforward::
+
+ 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. In
+this case, the lookup makes sense on all ``Field`` subclasses, so we register
+it with ``Field`` directly::
+
+ from django.db.models.fields import Field
+ Field.register_lookup(NotEqual)
+
+We can now use ``foo__ne`` for any field ``foo``. You will need to ensure that
+this registration happens before you try to create any querysets using it. You
+could place the implementation in a ``models.py`` file, or register the lookup
+in the ``ready()`` method of an ``AppConfig``.
+
+Taking a closer look at the implementation, the first required attribute is
+``lookup_name``. This allows the ORM to understand how to interpret ``name__ne``
+and use ``NotEqual`` to generate the SQL. By convention, these names are always
+lowercase strings containing only letters, but the only hard requirement is
+that it must not contain the string ``__``.
+
+A ``Lookup`` works against two values, ``lhs`` and ``rhs``, standing for
+left-hand side and right-hand side. The left-hand side is usually a field
+reference, but it can be anything implementing the :ref:`query expression API
+<query-expression>`. The right-hand is the value given by the user. In the
+example ``Author.objects.filter(name__ne='Jack')``, the left-hand side is a
+reference to the ``name`` field of the ``Author`` model, and ``'Jack'`` is the
+right-hand side.
+
+We call ``process_lhs`` and ``process_rhs`` to convert them into the values we
+need for SQL. In the above example, ``process_lhs`` returns
+``('"author"."name"', [])`` and ``process_rhs`` returns ``('"%s"', ['Jack'])``.
+In this example there were no parameters for the left hand side, but this would
+depend on the object we have, so we still need to include them in the
+parameters we return.
+
+Finally we combine the parts into a SQL expression with ``<>``, and supply all
+the parameters for the query. We then return a tuple containing the generated
+SQL string and the parameters.
+
+A simple transformer example
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The custom lookup above is great, but in some cases you may want to be able to
+chain lookups together. For example, let's suppose we are building an
+application where we want to make use of the ``abs()`` operator.
+We have an ``Experiment`` model which records a start value, end value and the
+change (start - end). We would like to find all experiments where the change
+was equal to a certain amount (``Experiment.objects.filter(change__abs=27)``),
+or where it did not exceede a certain amount
+(``Experiment.objects.filter(change__abs__lt=27)``).
+
+.. note::
+ This example is somewhat contrived, but it demonstrates nicely the range of
+ functionality which is possible in a database backend independent manner,
+ and without duplicating functionality already in Django.
+
+We will start by writing a ``AbsoluteValue`` transformer. This will use the SQL
+function ``ABS()`` to transform the value before comparison::
+
+ from django.db.models import Transform
+
+ class AbsoluteValue(Transform):
+ lookup_name = 'abs'
+
+ def as_sql(self, qn, connection):
+ lhs, params = qn.compile(self.lhs)
+ return "ABS(%s)" % lhs, params
+
+Next, lets register it for ``IntegerField``::
+
+ from django.db.models import IntegerField
+ IntegerField.register_lookup(AbsoluteValue)
+
+We can now run the queris we had before.
+``Experiment.objects.filter(change__abs=27)`` will generate the following SQL::
+
+ SELECT ... WHERE ABS("experiments"."change") = 27
+
+By using ``Transform`` instead of ``Lookup`` it means we are able to chain
+further lookups afterwards. So
+``Experiment.objects.filter(change__abs__lt=27)`` will generate the following
+SQL::
+
+ SELECT ... WHERE ABS("experiments"."change") < 27
+
+Subclasses of ``Transform`` usually only operate on the left-hand side of the
+expression. Further lookups will work on the transformed value. Note that in
+this case where there is no other lookup specified, Django interprets
+``change__abs=27`` as ``change__abs__exact=27``.
+
+When looking for which lookups are allowable after the ``Transform`` has been
+applied, Django uses the ``output_type`` attribute. We didn't need to specify
+this here as it didn't change, but supposing we were applying ``AbsoluteValue``
+to some field which represents a more complex type (for example a point
+relative to an origin, or a complex number) then we may have wanted to specify
+``output_type = FloatField``, which will ensure that further lookups like
+``abs__lte`` behave as they would for a ``FloatField``.
+
+Writing an efficient abs__lt lookup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using the above written ``abs`` lookup, the SQL produced will not use
+indexes efficiently in some cases. In particular, when we use
+``change__abs__lt=27``, this is equivalent to ``change__gt=-27`` AND
+``change__lt=27``. (For the ``lte`` case we could use the SQL ``BETWEEN``).
+
+So we would like ``Experiment.objects.filter(change__abs__lt=27)`` to generate
+the following SQL::
+
+ SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27
+
+The implementation is::
+
+ from django.db.models import Lookup
+
+ class AbsoluteValueLessThan(Lookup):
+ lookup_name = 'lt'
+
+ 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 > %s AND %s < -%s % (lhs, rhs, lhs, rhs), params
+
+ AbsoluteValue.register_lookup(AbsoluteValueLessThan)
+
+There are a couple of notable things going on. First, ``AbsoluteValueLessThan``
+isn't calling ``process_lhs()``. Instead it skips the transformation of the
+``lhs`` done by ``AbsoluteValue`` and uses the original ``lhs``. That is, we
+want to get ``27`` not ``ABS(27)``. Referring directly to ``self.lhs.lhs`` is
+safe as ``AbsoluteValueLessThan`` can be accessed only from the
+``AbsoluteValue`` lookup, that is the ``lhs`` is always an instance of
+``AbsoluteValue``.
+
+Notice also that as both sides are used multiple times in the query the params
+need to contain ``lhs_params`` and ``rhs_params`` multiple times.
+
+The final query does the inversion (``27`` to ``-27``) directly in the
+database. The reason for doing this is that if the self.rhs is something else
+than a plain integer value (for example an ``F()`` reference) we can't do the
+transformations in Python.
+
+.. note::
+ In fact, most lookups with ``__abs`` could be implemented as range queries
+ like this, and on most database backend it is likely to be more sensible to
+ do so as you can make use of the indexes. However with PostgreSQL you may
+ want to add an index on ``abs(change)`` which would allow these queries to
+ be very efficient.
+
+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. (Note that in reality almost all databases support both, including
+all the official databases supported by Django).
+
+We can change the behaviour on a specific backend by creating a subclass of
+``NotEqual`` with a ``as_mysql`` method::
+
+ 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)
+
+We can then register it with ``Field``. It takes the place of the original
+``NotEqual`` class as it has
+
+When compiling a query, Django first looks for ``as_%s % connection.vendor``
+methods, and then falls back to ``as_sql``. The vendor names for the in-built
+backends are ``sqlite``, ``postgresql``, ``oracle`` and ``mysql``.
+
+.. _query-expression:
+
+The Query Expression API
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+A lookup can assume that the lhs responds to the query expression API.
+Currently direct field references, aggregates and ``Transform`` 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.
+
+ Calling expression.as_sql() directly is usually incorrect - instead
+ qn.compile(expression) should be used. The qn.compile() method will take
+ care of calling vendor-specific methods of the expression.
+
+.. 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 in the same way
+ described in registering and fetching lookup documentation below.
+ It is possible to override this method to alter that behaviour.
+
+.. 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 built-in backends.
+
+.. 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.
+
+Note that this documentation lists only the public methods of the API.
+
+Lookup reference
+~~~~~~~~~~~~~~~~
+
+.. class:: Lookup
+
+ In addition to the attributes and methods below, lookups also support
+ ``as_sql`` and ``as_vendorname`` from the query expression API.
+
+.. attribute:: lhs
+
+ The ``lhs`` (left-hand side) of a lookup tells us what we are comparing the
+ rhs to. It is an object which implements the query expression API. This is
+ likely to be a field, an aggregate or a subclass of ``Transform``.
+
+.. attribute:: rhs
+
+ The ``rhs`` (right-hand side) of a lookup is the value we are comparing the
+ left hand side to. It may be a plain value, or something which compiles
+ into SQL, for example an ``F()`` object or a ``Queryset``.
+
+.. attribute:: lookup_name
+
+ This class level attribute is used when registering lookups. It determines
+ the name used in queries to trigger this lookup. For example, ``contains``
+ or ``exact``. This should not contain the string ``__``.
+
+.. method:: process_lhs(qn, connection)
+
+ This returns a tuple of ``(lhs_string, lhs_params)``. In some cases you may
+ wish to compile ``lhs`` directly in your ``as_sql`` methods using
+ ``qn.compile(self.lhs)``.
+
+.. method:: process_rhs(qn, connection)
+
+ Behaves the same as ``process_lhs`` but acts on the right-hand side.
+
+Transform reference
+~~~~~~~~~~~~~~~~~~~
+
+.. class:: Transform
+
+ In addition to implementing the query expression API Transforms have the
+ following methods and attributes.
+
+.. attribute:: lhs
+
+ The ``lhs`` (left-hand-side) of a transform contains the value to be
+ transformed. The ``lhs`` implements the query expression API.
+
+.. attribute:: lookup_name
+
+ This class level attribute is used when registering lookups. It determines
+ the name used in queries to trigger this lookup. For example, ``year``
+ or ``dayofweek``. This should not contain the string ``__``.
+
+.. _lookup-registration-api:
+
+Registering and fetching lookups
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The lookup registration API is explained below.
+
+.. classmethod:: register_lookup(lookup)
+
+ Registers the Lookup or Transform for the class. For example
+ ``DateField.register_lookup(YearExact)`` will register ``YearExact`` for
+ all ``DateFields`` in the project, but also for fields that are instances
+ of a subclass of ``DateField`` (for example ``DateTimeField``).
+
+.. method:: get_lookup(lookup_name)
+
+ Django uses ``get_lookup(lookup_name)`` to fetch lookups or transforms.
+ The implementation of ``get_lookup()`` fetches lookups or transforms
+ registered for the current class based on their lookup_name attribute.
+
+The lookup registration API is available for ``Transform`` and ``Field`` classes.
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index cc2a4cdcf4..2f62a6448b 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -343,6 +343,13 @@ underscores to spaces. See :ref:`Verbose field names <verbose-field-names>`.
A list of validators to run for this field. See the :doc:`validators
documentation </ref/validators>` for more information.
+Registering and fetching lookups
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``Field`` implements the :ref:`lookup registration API <lookup-registration-api>`.
+The API can be used to customize which lookups are available for a field class, and
+how lookups are fetched from a field.
+
.. _model-field-types:
Field types
diff --git a/docs/ref/models/index.txt b/docs/ref/models/index.txt
index c61bb35a5f..4716c03f95 100644
--- a/docs/ref/models/index.txt
+++ b/docs/ref/models/index.txt
@@ -13,3 +13,4 @@ Model API reference. For introductory material, see :doc:`/topics/db/models`.
instances
querysets
queries
+ custom-lookups
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index ca9bf1dbd1..c2a3981567 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -1995,6 +1995,9 @@ specified as keyword arguments to the ``QuerySet`` methods :meth:`filter()`,
For an introduction, see :ref:`models and database queries documentation
<field-lookups-intro>`.
+Django's inbuilt lookups are listed below. It is also possible to write
+:doc:`custom lookups </ref/models/custom-lookups>` for model fields.
+
.. fieldlookup:: exact
exact
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 671b5878b9..0525bb2e12 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -180,6 +180,27 @@ for the following, instead of backend specific behavior.
finally:
c.close()
+Custom lookups
+~~~~~~~~~~~~~~
+
+It is now possible to write custom lookups and transforms for the ORM.
+Custom lookups work just like Django's inbuilt lookups (e.g. ``lte``,
+``icontains``) while transforms are a new concept.
+
+The :class:`django.db.models.Lookup` class provides a way to add lookup
+operators for model fields. As an example it is possible to add ``day_lte``
+opertor for ``DateFields``.
+
+The :class:`django.db.models.Transform` class allows transformations of
+database values prior to the final lookup. For example it is possible to
+write a ``year`` transform that extracts year from the field's value.
+Transforms allow for chaining. After the ``year`` transform has been added
+to ``DateField`` it is possible to filter on the transformed value, for
+example ``qs.filter(author__birthdate__year__lte=1981)``.
+
+For more information about both custom lookups and transforms refer to
+:doc:`custom lookups </ref/models/custom-lookups>` documentation.
+
Minor features
~~~~~~~~~~~~~~