Wagtail’s built in search functionality has this nifty feature where it will index callables on your model, e.g. your model has a start and end date, but you want to search on duration. Hypothetically we could add a FilterField here to index this for Elasticsearch ((Note the requirement for a type, otherwise this will be indexed as a string.)).
[python]
class Job(Model, index.Indexed):
"""A job you can apply for."""
start_date = models.DateField()
end_date = models.DateField()
search_fields = (
index.FilterField(‘duration’, type=’IntegerField’),
)
@property
def duration(self):
"""Duration of the assignment."""
return 12 * (self.end_date.year – self.start_date.year) + \
self.end_date.month – self.start_date.month
[/python]
Wagtail is quite clever in that it takes a Django QuerySet and decomposes the filters.
[python]
queryset = queryset\
.filter(duration__range=(data[‘duration’].lower or 0,
data[‘duration’].upper or 99999))
query = backend.search(keywords, queryset)
[/python]
Of course Django will get upset about this, since that’s not a field you can filter on. So we can annotate the value in.
[python highlight=”14-16″]
from django.db.models import Func, fields
class DurationInMonths(Func): # pylint:disable=abstract-method
"""
SQL Function to calculate the duration of assignments in months.
"""
template = \
‘EXTRACT(year FROM age(%(expressions)s)) * 12 + ‘ \
‘EXTRACT(month FROM age(%(expressions)s))’
output_field = fields.IntegerField()
queryset = queryset\
.annotate(duration=DurationInMonths(
F(‘end_date’), F(‘start_date’)
))\
.filter(duration__range=(data[‘duration’].lower or 0,
data[‘duration’].upper or 99999))
[/python]
Also Django will be upset it can’t annotate the duration, so you’ll need to add a setter to your model.
[python highlight=”6-9″]
class Job(Model, index.Indexed):
@property
def duration(self):
…
@duration.setter
def duration(self, value):
"""Ignored to make Django annotations happy."""
pass
[/python]
And you ideally would be done except now Wagtail gets upset that it can’t determine the attribute name for the field but you can kludge around this for now:
[python highlight=”9-11″]
class DurationInMonths(Func): # pylint:disable=abstract-method
"""
SQL Function to calculate the duration of assignments in months.
"""
template = \
‘EXTRACT(year FROM age(%(expressions)s)) * 12 + ‘ \
‘EXTRACT(month FROM age(%(expressions)s))’
output_field = fields.IntegerField()
target = type(‘IntegerFieldKludge’,
(fields.IntegerField,),
{‘attname’: ‘duration’})()
[/python]
