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 .. 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 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) query conditions. For example in the query Book.filter(author__age__lte=30)
the author__age__lte is the lookup path. the part "author__age__lte" is the lookup path.
The lookup path consist of three different part. First is the related lookups, The lookup path consist of three different parts. First is the related
above part author refers to Book's related model Author. Second part of the lookups. In the author__age__lte example the part author refers to Book's
lookup path is the final field, above this is Author's field age. Finally the related model Author. Second part of the lookup path is the field. This is
lte part is commonly called just lookup (TODO: this nomenclature is confusing, Author's age field in the example. Finally the lte part is commonly called
can we invent something better). 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 This documentation concentrates on writing custom lookups. By writing custom
implementations for lte or any other lookup you wish to use. 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 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 1. Return a Lookup class
2. Raise a FieldError 2. Raise a FieldError
3. Return None 3. Return None
Above return None is only available during backwards compatibility period and Returning None is only available during backwards compatibility period.
returning None will not be allowed in Django 1.9 or later. The interpretation The interpretation is to use the old way of lookup hadling inside the ORM.
is to use the old way of lookup hadling inside the ORM.
The returned Lookup will be used to build the query.
The Lookup class 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: The API is as follows:
.. attribute:: lookup_name .. 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) .. method:: __init__(lhs, rhs)
The lhs and rhs are the field reference (reference to field age in the The lhs is something implementing the query expression API. For example in
author__age__lte=30 example), and rhs is the value (30 in the example). 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 .. attribute:: Lookup.lhs
The left hand side part of this lookup. You can assume it implements the 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) .. 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 backends have ability to customize the produced query string. More of this
later on. later on.
The connection is the used connection. The connection is the connection the SQL is compiled against.
.. 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.
In addition the Lookup class has some private methods - that is, implementing In addition the Lookup class has some private methods - that is, implementing
just the above mentioned attributes and methods is not enough, instead you just the above mentioned attributes and methods is not enough, instead you
should subclass Lookup. must subclass Lookup.
The Extract class The Extract class
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
An Extract is something that converts a value to another value in the query string. An Extract is something that converts a value to another value in the query
For example you could have an Extract that procudes modulo 3 of the given value. string. For example you could have an Extract that procudes modulo 3 of the
In SQL this would be something like "author"."age" % 3. given value. In SQL this is something like "author"."age" % 3.
Extracts are used in nested lookups. The Extract class must implement the query Extracts are used in nested lookups. The Extract class must implement the
part interface. In addition the Extract should must lookup_name attribute. query part interface.
Extracts should be written by subclassing django.db.models.Extract.
A simple Lookup example 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 from django.db.models import Lookup, IntegerField
class Div3(Lookup): class Mod3(Lookup):
lookup_name = 'div3' lookup_name = 'mod3'
def as_sql(self, qn, connection): def as_sql(self, qn, connection):
lhs_sql, params = self.process_lhs(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) IntegerField.register_lookup(Div3)
Now all IntegerFields or subclasses of IntegerField will have 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. This query would return every author whose age % 3 == 2.
A simple nested lookup example A simple nested lookup example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is how to write an Extract and a Lookup for IntegerField. The 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:: support nesting lookups::
class Div3Extract(Extract): class Mod3Extract(Extract):
lookup_name = 'div3' lookup_name = 'mod3'
def as_sql(self, qn, connection): def as_sql(self, qn, connection):
lhs, lhs_params = qn.compile(self.lhs) lhs, lhs_params = qn.compile(self.lhs)
return '%s %%%% 3' % (lhs,), lhs_params 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 Note that if you already added Mod3 for IntegerField in the above
example, now Div3LookupWithExtract will override that lookup. 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 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 lhs' output_type. So, the Mod3Extract supports all the same lookups as
IntegerField. For example Author.objects.filter(age__div3__in=[1, 2]) IntegerField. For example Author.objects.filter(age__mod3__in=[1, 2])
returns all authors for which age % 3 in (1, 2). returns all authors for which age % 3 in (1, 2).
A more complex nested lookup A more complex nested lookup