Some rewording in docs

This commit is contained in:
Anssi Kääriäinen 2014-01-11 13:16:01 +02:00
parent 31b8faa627
commit 66649ff891
1 changed files with 62 additions and 57 deletions

View File

@ -7,56 +7,71 @@ Custom lookups
.. currentmodule:: django.db.models
(This documentation is candidate for complete rewrite, but contains
useful information of how to test the current implementation.)
This documentation constains instructions of how to create custom lookups
for model fields.
Django's ORM works using lookup paths when building query filters and other
query structures. For example in the query Book.filter(author__age__lte=30)
the author__age__lte is the lookup path.
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 part. First is the related lookups,
above part author refers to Book's related model Author. Second part of the
lookup path is the final field, above this is Author's field age. Finally the
lte part is commonly called just lookup (TODO: this nomenclature is confusing,
can we invent something better).
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, that is custom
implementations for lte or any other lookup you wish to use.
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 can do three things:
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
Above return None is only available during backwards compatibility period and
returning None will not be allowed in Django 1.9 or later. The interpretation
is to use the old way of lookup hadling inside the ORM.
The returned Lookup will be used to build the query.
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.
A string used by Django to distinguish different lookups. For example
'exact'.
.. method:: __init__(lhs, rhs)
The lhs and rhs are the field reference (reference to field age in the
author__age__lte=30 example), and rhs is the value (30 in the example).
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 part interface (TODO: write interface definition...).
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)
@ -79,42 +94,32 @@ 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 used connection.
.. method:: Lookup.process_lhs(qn, connection, lhs=None)
This method is used to convert the left hand side of the lookup into query
string. The left hand side can be a field reference or a nested lookup. The
lhs kwarg can be used to convert something else than self.lhs to query string.
.. method:: Lookup.process_rhs(qn, connection, rhs=None)
The process_rhs method is used to convert the right hand side into query string.
The rhs is the value given in the filter clause. It can be a raw value to
compare agains, a F() reference to another field or even a QuerySet.
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
should subclass Lookup.
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 would be something like "author"."age" % 3.
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. In addition the Extract should must lookup_name attribute.
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 div3 lookup for IntegerField::
This is how to write a simple mod3 lookup for IntegerField::
from django.db.models import Lookup, IntegerField
class Div3(Lookup):
lookup_name = 'div3'
class Mod3(Lookup):
lookup_name = 'mod3'
def as_sql(self, qn, connection):
lhs_sql, params = self.process_lhs(qn, connection)
@ -126,32 +131,32 @@ This is how to write a simple div3 lookup for IntegerField::
IntegerField.register_lookup(Div3)
Now all IntegerFields or subclasses of IntegerField will have
a div3 lookup. For example you could do Author.objects.filter(age__div3=2).
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 div3 lookup, and in addition it
lookup can be used similarly as the above mod3 lookup, and in addition it
support nesting lookups::
class Div3Extract(Extract):
lookup_name = 'div3'
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(Div3Extract)
IntegerField.register_lookup(Mod3Extract)
Note that if you already added Div3 for IntegerField in the above
example, now Div3LookupWithExtract will override that lookup.
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 Div3 lookup, but in addition it supports
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 Div3Extract supports all the same lookups as
IntegerField. For example Author.objects.filter(age__div3__in=[1, 2])
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