custom-lookups.txt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. ==============
  2. Custom Lookups
  3. ==============
  4. .. versionadded:: 1.7
  5. .. currentmodule:: django.db.models
  6. Django offers a wide variety of :ref:`built-in lookups <field-lookups>` for
  7. filtering (for example, ``exact`` and ``icontains``). This documentation
  8. explains how to write custom lookups and how to alter the working of existing
  9. lookups. For the API references of lookups, see the :doc:`/ref/models/lookups`.
  10. A simple lookup example
  11. ~~~~~~~~~~~~~~~~~~~~~~~
  12. Let's start with a simple custom lookup. We will write a custom lookup ``ne``
  13. which works opposite to ``exact``. ``Author.objects.filter(name__ne='Jack')``
  14. will translate to the SQL::
  15. "author"."name" <> 'Jack'
  16. This SQL is backend independent, so we don't need to worry about different
  17. databases.
  18. There are two steps to making this work. Firstly we need to implement the
  19. lookup, then we need to tell Django about it. The implementation is quite
  20. straightforward::
  21. from django.db.models import Lookup
  22. class NotEqual(Lookup):
  23. lookup_name = 'ne'
  24. def as_sql(self, qn, connection):
  25. lhs, lhs_params = self.process_lhs(qn, connection)
  26. rhs, rhs_params = self.process_rhs(qn, connection)
  27. params = lhs_params + rhs_params
  28. return '%s <> %s' % (lhs, rhs), params
  29. To register the ``NotEqual`` lookup we will just need to call
  30. ``register_lookup`` on the field class we want the lookup to be available. In
  31. this case, the lookup makes sense on all ``Field`` subclasses, so we register
  32. it with ``Field`` directly::
  33. from django.db.models.fields import Field
  34. Field.register_lookup(NotEqual)
  35. We can now use ``foo__ne`` for any field ``foo``. You will need to ensure that
  36. this registration happens before you try to create any querysets using it. You
  37. could place the implementation in a ``models.py`` file, or register the lookup
  38. in the ``ready()`` method of an ``AppConfig``.
  39. Taking a closer look at the implementation, the first required attribute is
  40. ``lookup_name``. This allows the ORM to understand how to interpret ``name__ne``
  41. and use ``NotEqual`` to generate the SQL. By convention, these names are always
  42. lowercase strings containing only letters, but the only hard requirement is
  43. that it must not contain the string ``__``.
  44. We then need to define the ``as_sql`` method. This takes a ``SQLCompiler``
  45. object, called ``qn``, and the active database connection. ``SQLCompiler``
  46. objects are not documented, but the only thing we need to know about them is
  47. that they have a ``compile()`` method which returns a tuple containing a SQL
  48. string, and the parameters to be interpolated into that string. In most cases,
  49. you don't need to use it directly and can pass it on to ``process_lhs()`` and
  50. ``process_rhs()``.
  51. A ``Lookup`` works against two values, ``lhs`` and ``rhs``, standing for
  52. left-hand side and right-hand side. The left-hand side is usually a field
  53. reference, but it can be anything implementing the :ref:`query expression API
  54. <query-expression>`. The right-hand is the value given by the user. In the
  55. example ``Author.objects.filter(name__ne='Jack')``, the left-hand side is a
  56. reference to the ``name`` field of the ``Author`` model, and ``'Jack'`` is the
  57. right-hand side.
  58. We call ``process_lhs`` and ``process_rhs`` to convert them into the values we
  59. need for SQL using the ``qn`` object described before. These methods return
  60. tuples containing some SQL and the parameters to be interpolated into that SQL,
  61. just as we need to return from our ``as_sql`` method. In the above example,
  62. ``process_lhs`` returns ``('"author"."name"', [])`` and ``process_rhs`` returns
  63. ``('"%s"', ['Jack'])``. In this example there were no parameters for the left
  64. hand side, but this would depend on the object we have, so we still need to
  65. include them in the parameters we return.
  66. Finally we combine the parts into a SQL expression with ``<>``, and supply all
  67. the parameters for the query. We then return a tuple containing the generated
  68. SQL string and the parameters.
  69. A simple transformer example
  70. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  71. The custom lookup above is great, but in some cases you may want to be able to
  72. chain lookups together. For example, let's suppose we are building an
  73. application where we want to make use of the ``abs()`` operator.
  74. We have an ``Experiment`` model which records a start value, end value, and the
  75. change (start - end). We would like to find all experiments where the change
  76. was equal to a certain amount (``Experiment.objects.filter(change__abs=27)``),
  77. or where it did not exceed a certain amount
  78. (``Experiment.objects.filter(change__abs__lt=27)``).
  79. .. note::
  80. This example is somewhat contrived, but it nicely demonstrates the range of
  81. functionality which is possible in a database backend independent manner,
  82. and without duplicating functionality already in Django.
  83. We will start by writing a ``AbsoluteValue`` transformer. This will use the SQL
  84. function ``ABS()`` to transform the value before comparison::
  85. from django.db.models import Transform
  86. class AbsoluteValue(Transform):
  87. lookup_name = 'abs'
  88. def as_sql(self, qn, connection):
  89. lhs, params = qn.compile(self.lhs)
  90. return "ABS(%s)" % lhs, params
  91. Next, lets register it for ``IntegerField``::
  92. from django.db.models import IntegerField
  93. IntegerField.register_lookup(AbsoluteValue)
  94. We can now run the queries we had before.
  95. ``Experiment.objects.filter(change__abs=27)`` will generate the following SQL::
  96. SELECT ... WHERE ABS("experiments"."change") = 27
  97. By using ``Transform`` instead of ``Lookup`` it means we are able to chain
  98. further lookups afterwards. So
  99. ``Experiment.objects.filter(change__abs__lt=27)`` will generate the following
  100. SQL::
  101. SELECT ... WHERE ABS("experiments"."change") < 27
  102. Subclasses of ``Transform`` usually only operate on the left-hand side of the
  103. expression. Further lookups will work on the transformed value. Note that in
  104. this case where there is no other lookup specified, Django interprets
  105. ``change__abs=27`` as ``change__abs__exact=27``.
  106. When looking for which lookups are allowable after the ``Transform`` has been
  107. applied, Django uses the ``output_field`` attribute. We didn't need to specify
  108. this here as it didn't change, but supposing we were applying ``AbsoluteValue``
  109. to some field which represents a more complex type (for example a point
  110. relative to an origin, or a complex number) then we may have wanted to specify
  111. ``output_field = FloatField``, which will ensure that further lookups like
  112. ``abs__lte`` behave as they would for a ``FloatField``.
  113. Writing an efficient abs__lt lookup
  114. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  115. When using the above written ``abs`` lookup, the SQL produced will not use
  116. indexes efficiently in some cases. In particular, when we use
  117. ``change__abs__lt=27``, this is equivalent to ``change__gt=-27`` AND
  118. ``change__lt=27``. (For the ``lte`` case we could use the SQL ``BETWEEN``).
  119. So we would like ``Experiment.objects.filter(change__abs__lt=27)`` to generate
  120. the following SQL::
  121. SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27
  122. The implementation is::
  123. from django.db.models import Lookup
  124. class AbsoluteValueLessThan(Lookup):
  125. lookup_name = 'lt'
  126. def as_sql(self, qn, connection):
  127. lhs, lhs_params = qn.compile(self.lhs.lhs)
  128. rhs, rhs_params = self.process_rhs(qn, connection)
  129. params = lhs_params + rhs_params + lhs_params + rhs_params
  130. return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params
  131. AbsoluteValue.register_lookup(AbsoluteValueLessThan)
  132. There are a couple of notable things going on. First, ``AbsoluteValueLessThan``
  133. isn't calling ``process_lhs()``. Instead it skips the transformation of the
  134. ``lhs`` done by ``AbsoluteValue`` and uses the original ``lhs``. That is, we
  135. want to get ``27`` not ``ABS(27)``. Referring directly to ``self.lhs.lhs`` is
  136. safe as ``AbsoluteValueLessThan`` can be accessed only from the
  137. ``AbsoluteValue`` lookup, that is the ``lhs`` is always an instance of
  138. ``AbsoluteValue``.
  139. Notice also that as both sides are used multiple times in the query the params
  140. need to contain ``lhs_params`` and ``rhs_params`` multiple times.
  141. The final query does the inversion (``27`` to ``-27``) directly in the
  142. database. The reason for doing this is that if the self.rhs is something else
  143. than a plain integer value (for example an ``F()`` reference) we can't do the
  144. transformations in Python.
  145. .. note::
  146. In fact, most lookups with ``__abs`` could be implemented as range queries
  147. like this, and on most database backends it is likely to be more sensible to
  148. do so as you can make use of the indexes. However with PostgreSQL you may
  149. want to add an index on ``abs(change)`` which would allow these queries to
  150. be very efficient.
  151. Writing alternative implementations for existing lookups
  152. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  153. Sometimes different database vendors require different SQL for the same
  154. operation. For this example we will rewrite a custom implementation for
  155. MySQL for the NotEqual operator. Instead of ``<>`` we will be using ``!=``
  156. operator. (Note that in reality almost all databases support both, including
  157. all the official databases supported by Django).
  158. We can change the behavior on a specific backend by creating a subclass of
  159. ``NotEqual`` with a ``as_mysql`` method::
  160. class MySQLNotEqual(NotEqual):
  161. def as_mysql(self, qn, connection):
  162. lhs, lhs_params = self.process_lhs(qn, connection)
  163. rhs, rhs_params = self.process_rhs(qn, connection)
  164. params = lhs_params + rhs_params
  165. return '%s != %s' % (lhs, rhs), params
  166. Field.register_lookup(MySQLNotExact)
  167. We can then register it with ``Field``. It takes the place of the original
  168. ``NotEqual`` class as it has the same ``lookup_name``.
  169. When compiling a query, Django first looks for ``as_%s % connection.vendor``
  170. methods, and then falls back to ``as_sql``. The vendor names for the in-built
  171. backends are ``sqlite``, ``postgresql``, ``oracle`` and ``mysql``.
  172. How Django determines the lookups and transforms which are used
  173. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  174. In some cases you may wish to dynamically change which ``Transform`` or
  175. ``Lookup`` is returned based on the name passed in, rather than fixing it. As
  176. an example, you could have a field which stores coordinates or an arbitrary
  177. dimension, and wish to allow a syntax like ``.filter(coords__x7=4)`` to return
  178. the objects where the 7th coordinate has value 4. In order to do this, you
  179. would override ``get_lookup`` with something like::
  180. class CoordinatesField(Field):
  181. def get_lookup(self, lookup_name):
  182. if lookup_name.startswith('x'):
  183. try:
  184. dimension = int(lookup_name[1:])
  185. except ValueError:
  186. pass
  187. finally:
  188. return get_coordinate_lookup(dimension)
  189. return super(CoordinatesField, self).get_lookup(lookup_name)
  190. You would then define ``get_coordinate_lookup`` appropriately to return a
  191. ``Lookup`` subclass which handles the relevant value of ``dimension``.
  192. There is a similarly named method called ``get_transform()``. ``get_lookup()``
  193. should always return a ``Lookup`` subclass, and ``get_transform()`` a
  194. ``Transform`` subclass. It is important to remember that ``Transform``
  195. objects can be further filtered on, and ``Lookup`` objects cannot.
  196. When filtering, if there is only one lookup name remaining to be resolved, we
  197. will look for a ``Lookup``. If there are multiple names, it will look for a
  198. ``Transform``. In the situation where there is only one name and a ``Lookup``
  199. is not found, we look for a ``Transform`` and then the ``exact`` lookup on that
  200. ``Transform``. All call sequences always end with a ``Lookup``. To clarify:
  201. - ``.filter(myfield__mylookup)`` will call ``myfield.get_lookup('mylookup')``.
  202. - ``.filter(myfield__mytransform__mylookup)`` will call
  203. ``myfield.get_transform('mytransform')``, and then
  204. ``mytransform.get_lookup('mylookup')``.
  205. - ``.filter(myfield__mytransform)`` will first call
  206. ``myfield.get_lookup('mytransform')``, which will fail, so it will fall back
  207. to calling ``myfield.get_transform('mytransform')`` and then
  208. ``mytransform.get_lookup('exact')``.