Project Proposal
Project Proposal
Field-instance
Lookups
GSOC 2022
]\
Table of Contents
1. Abstract 2
2. Django Lookups 3
2.1 Django Lookup API 3
2.2 Custom Lookups 4
2.3 RegisterLookupMixin 5
3. Limitations of current 6
5. Proposed Implementation 10
5.1 Overview 10
5.2 Names for methods 10
5.3 Differentiating class calls and instance calls 11
5.4 Storing lookups 12
5.5 get_lookups() 13
5.6 get_lookup() 14
5.7 register_lookup() and unregister_lookup() 14
5.8 Per-model custom lookups 16
16
5.9 Additional Features (optional) 17
6. Timeline 18
6.1 Phase 1 - June 13 to July 25 (6 weeks) 18
6.2 Phase 2 - July 25 to September 12(tentative) 19
6.3 Pre-GSOC and community bonding period 21
7. Previous work 21
About Me 22
1
]\
1. Abstract
This proposal aims to add support to register and unregister custom
lookups on a model’s Field instances rather than on a model’s Field class.
As of now, the Lookup API can only register and unregister Field
classes. So a custom lookup added or overridden to a field affects all models
which have that field. This limits the customizability of model lookups. In
some cases, a user would like to have a custom lookup for one specific model.
This would be a great addition to Django lookups that will allow users
to further customize their model’s lookups.
Note: The code written throughout is just to convey my ideas - the actual
implementation might be different.
2
]\
2. Django Lookups
2.1 Django Lookup API
The Lookup API constructs the WHERE clause of an SQL query. The
Lookup API consists of two parts - ‘RegisterLookupMixin’ class which registers
the lookup(s) and a Query expression API which translates the lookup into
SQL expressions.
>>> Employee.objects.filter(salary__gt=20000)
3
]\
class LessEqual(Lookup):
lookup_name = 'le'
The above custom lookup is registered to the Field class. This applies the
lookup to all fields which inherit from Field class (IntergerField, CharField,
etc.). But realistically, you would want this lookup to be used only for an
IntergerField.
4
]\
2.3 RegisterLookupMixin
A mixin that implements the lookup API on a class. It has the following
methods -
register_lookup()
Registers a custom lookup in the class.
get_lookup()
Returns the Lookup named ‘lookup_name’ registered in the class. The default
implementation looks recursively on all parent classes and checks if any has a
registered lookup named lookup_name, returning the first match.
get_lookups()
Returns a dictionary of each lookup name registered in the class mapped to
the Lookup class.
get_transform()
Returns a Transform named ‘transform_name’.
5
]\
3. Limitations of current
implementation
RegisterLookupMixin provides support for registering lookups only for the
Field class or its subclasses (CharField, IntegerField, DateTimeField, etc). This
was done because it was easier to implement.
@CharField.register_lookup
class NotEqual(Lookup):
lookup_name = 'nte'
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return '%s <> %s' % (lhs, rhs), params
6
]\
This, however, would also affect the ‘department’ field (because it's also a
CharField) and also any other Model which has a CharField attribute. This
deteriorates the ability of the custom lookup system in Django.
Similarly unregistering a lookup would disable the lookup for all field classes
in all models created. Same for overriding lookups.
Adding the per-instance field lookup field would be a great addition to the
Django lookup system.
7
]\
class AbsoluteValueLessThan(Lookup):
lookup_name = 'lt'
8
]\
If we would want to override a lookup for the age instance of the Employee
class, it would be possible. The syntax would be as follows after the code
implementation is done.
age.register_lookup(AbsoluteLessThan)
name.unregister_lookup(Contains)
This would enable users to disable costly lookups for third-party applications
that don't require contains.
Employee.register_lookup(Field, lookup)
I think this would be the best approach for users who need custom lookups
for every model, not necessarily for every instance. The same thing could be
done with unregistering lookups as well.
9
]\
5. Proposed Implementation
5.1 Overview
Two WIP pull requests are already implemented in #11408 and
charettes@ccc58bf. Both the implementations are good but different. I prefer
charettes’ implementation and think it would fit the idea I have in mind.
Some tweaking and additional improvements and optimization are all it
needs. Apart from registering and unregistering lookups per field instance, I
would also like to extend support for Registering lookups for Models. I believe
it would be a good addition.
Field.register_class_lookup(lookup)
Field.register_instance_lookup(lookup)
10
]\
This could be not such a good idea because - the existing method for
registering lookups would be changed (register_lookup to
register_class_lookup), thus affecting users getting used to the change. It is
possible to implement a common method register_lookup that registers
classes or instances based on its arguments.
class classorinstancemethod(object):
def __init__(self, class_method, instance_method):
self.class_method = class_method
self.instance_method = instance_method
11
]\
register_lookup = classorinstancemethod(register_class_lookup,
register_instance_lookup)
_unregister_lookup = classorinstancemethod(_unregister_class_lookup,
_unregister_lookup)
This would allow all using methods to have the same method names without
harming the current implementation. We would have separate methods for
class calls and instance calls merged by the classorinstancemethod class.
For Classes
This is done by a class variable class_lookups which enables all child classes
to possess this data. class_lookups is a dictionary that maps lookup_names to
lookups.
For Instances
12
]\
5.5 get_lookups()
get_lookups() method would have to have two separate methods for getting
instance lookups and getting class lookup.
5.5.1 get_class_lookups()
5.5.2 get_instance_lookups()
This would fetch all the lookups present in the instance. This would be -
● All the class_lookups of the class of the instance.
● Extra registered instance_lookups for that particular instance.
So it would look something like this -
def get_instance_lookups(self):
# get class lookups
class_lookups = self.get_class_lookups()
# get instance lookups
instance_lookups_only = getattr(self, instance_lookups)
# merging dictionaries
instance_lookups = {**class_lookups, **instance_lookups_only}
return instance_lookups
13
]\
5.6 get_lookup()
This method would return a lookup named lookup_name. This also needs to
have separate methods for classes and instances merged by get_lookup()
partial method return by classorinstancemethod class.
5.6.1 get_class_lookup()
5.6.2 get_instance_lookup()
This method is would be similar to the above method but would called by an
instance. Support for this could also prove useful (not done in
charettes@ccc58bf).
14
]\
Employee.register_lookup(IntegerField, AbosoluteLessThan)
15
]\
Mock implementation -
@classmethod
def register_lookup(cls, field, lookup):
fields = cls.fields # just for demonstration
for field in fields:
field.register_lookup(lookup)
16
]\
17
]\
6. Timeline
Since the project is partially done in #11408 and charettes@ccc58bf, I
think it would likely be a moderately long project. Already implemented
methods would only need to be improved and optimized and further tested
and documented (which in my opinion would not take a lot of time). New
methods would take some time to implement from scratch.
Even though some work is already done, completing the project in 175
hours might not be possible if any problem arises during the course of doing
it. To make sure to give the best possible output, a 350-hour project would
provide enough time to overcome any problems which would arise and
would allow me to add additional features to this project.
18
]\
GSOC this year would be flexible with time, so the second phase time period
would differ for different projects.
19
]\
If I could finish by this time I will submit the project within the standard
coding period. If in any case that is not possible, I will complete the remaining
work during the extended coding period which goes on till November 13.
During the remaining time, I could work on additional features if anything
looks promising.
20
]\
7. Previous work
I am a beginner to open source, so I have not done a huge deal of
contributions before. But I have worked on two merged pull requests in
Django.
Pull Requests(merged)
I have also made a few projects with Django (a blog application) and also
have built web applications using HTML, CSS, Javascript and Node.js.
21
]\
About Me
My name is Allen Jonathan David and I am a student at Vellore Institute of
Technology University in Vellore, India. My time zone is UTC+05:30. I have been
coding in python for two years now and can also code in Java and Javascript. I
have contributed a couple of pull requests to Django already and am
confident I can take on this task.
Timeline - UTC+05:30
Email - [email protected]
Github - AllenJonathan
Phone - 91-8778994400
22