EDIT: Added better description
I am using django-haystack 2.3.1 with Elasticsearch backend, Django 1.7, and Python 3.4
In my application I have a Vendor model and a Location model. Locations contain full US postal address, latitude/longitude, and a GEOS point object. Vendors have a ManyToManyField with Locations, maintaining that multiple Vendors can be available at one Location, and one Location can service multiple vendors. When a user searches for a vendor, I need to filter search results to only display vendors that are available at one or more locations within 5 miles of a specified Zip Code (geocoded to latitude and longitude by Nominatim).
If vendors only had one associated location this would be easy with django-haystack. I could make my index
class VendorIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
location = indexes.LocationField(model_attr='location__point')
# Other fields
and add the following line to the search() method of my search form:
# Check to see if a start_date was chosen.
if self.cleaned_data['zip_code']:
location = self.geolocator.geocode(str(self.cleaned_data['zip_code']), timeout=10)
point = Point(location.longitude, location.latitude)
sqs = sqs.dwithin(
'location', point, D(mi=settings.SEARCH_MILE_RADIUS),
)
The problem is searching through all the locations at which a vendor may be offered. In Geodjango queries, I could just represent the Location as a MultiPoint... but haystack only supports single point objects. I don't need to find the number of the vendor's locations that are within the 5 mile radius, I just need to filter out all vendors that do not contain at least one location that is within the required distance of the input location.
There must be some way that I can achieve this, right?
The elasticsearch spatial query documentation contains the following note:
Multi Location Per Document
The geo_distance filter can work with multiple locations points per document. Once a single location point matches the filter, the document will be included in the filter.
So clearly elasticsearch can support what I want to do. Now I need to know if haystack can support this as well or if I need to do some hacking around haystack.
So I solved this problem by creating a custom field type to handle multiple locations.
If anyone else needs multiple locations for a single model instance with django-haystack, like I did, you can download this gist from github to use the MultiLocationField. It works for sure with elasticsearch backend. I'm not positive about other backends.
Semi-Related:
Also note that there is a bug in django-haystack with elasticsearch involving computing distances. When you specify a distance using D(mi=10) (obviously you can choose your units and distance, mi and 10 are just placeholders), django-haystack then converts that to km and sends it as the distance parameter to elasticsearch without specifying units. Since the default distance unit for elasticsearch is meters, elasticsearch interprets this km distance as meters, so it will be off by a factor of 1000. I wasted a long time trying to figure out what was wrong with my code before I realized this was a bug. I got around it by doing something like D(km=5000) when I really wanted a distance of 5 km. Not sure why django-haystack doesn't just make a quick change to fix this bug.