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.)).
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
Wagtail is quite clever in that it takes a Django QuerySet and decomposes the filters.
queryset = queryset\ .filter(duration__range=(data['duration'].lower or 0, data['duration'].upper or 99999)) query = backend.search(keywords, queryset)
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.
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))
Also Django will be upset it can’t annotate the duration, so you’ll need to add a setter to your model.
class Job(Model, index.Indexed): @property def duration(self): ... @duration.setter def duration(self, value): """Ignored to make Django annotations happy.""" pass
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:
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'})()