barseghyanartur / django-elasticsearch-dsl-drf

Integrate Elasticsearch DSL with Django REST framework.
https://pypi.python.org/pypi/django-elasticsearch-dsl-drf
365 stars 116 forks source link

A list of locations - geo_spatial_filter_fields #206

Open belthaZornv opened 4 years ago

belthaZornv commented 4 years ago

I just converted a single location field on my document, to a NestedField with this definition:

location = fields.NestedField(properties={"point": fields.GeoPointField()})

Then on my view I have (I added path and changed the field value to try and make it work):

    geo_spatial_filter_fields = {
        'location': {
            'path': 'location',
            'field': 'point',
            'lookups': [constants.LOOKUP_FILTER_GEO_DISTANCE]
        }
    }

    geo_spatial_ordering_fields = {'location': None}

I'm wondering how this can be achieved? I want to order all the documents based on the closest location from a list of locations for each document.

Edit

Currently experimenting with:

{
   "query":{
      "nested":{
         "path":"location",
         "query":{
            "geo_distance":{
               "distance":"16090km",
               "location.point":{
                  "lat":"52.240995",
                  "lon":"0.751156"
               },
               "distance_type":"arc"
            }
         }
      }
   },
   "sort":[
      {
         "_geo_distance":{
            "location.point":{
               "lat":"52.240995",
               "lon":"0.751156"
            },
            "unit":"km",
            "distance_type":"plane",
            "order":"asc"
         }
      },
      {
         "date_first_registered":{
            "order":"desc"
         }
      }
   ]
}

The filtration seems to be working, the sorting, not!

I appreciate your time, Thanks

belthaZornv commented 4 years ago

Solved it by using:

{
   "query":{
      "nested":{
         "path":"location",
         "query":{
            "geo_distance":{
               "distance":"16090km",
               "location.point":{
                  "lat":52.240995,
                  "lon":0.751156
               },
               "distance_type":"arc"
            }
         }
      }
   },
   "sort":[
      {
         "_geo_distance":{
            "nested_path": "location",
            "location.point":{
               "lat":52.240995,
               "lon":0.751156
            },
            "unit":"km",
            "distance_type":"plane",
            "order":"asc"
         }
      },
      {
         "date_first_registered":{
            "order":"desc"
         }
      }
   ]
}

Not sure if there is a cleaner way of doing it, but I ended up doing:

def attach_nested_path_to_queryset(queryset: Search):
    """
    Attach nested path to the query and sort keys in the queryset
    and update the queryset using `update_from_dict`

    **The updating is done by reference**

    :param queryset: the original queryset
    :type queryset: Search

    :return:
    """
    queryset_dict = queryset.to_dict()

    attach_nested_path_to_query(queryset_dict)
    attach_nested_path_to_sort(queryset_dict)

    # update queryset
    queryset.update_from_dict(queryset_dict)

def attach_nested_path_to_query(queryset_dict: dict):
    """
    Looks for geo_distance in the queryset dict, if it's at the top level
    we modify the top level query, meaning that there's only one query, otherwise
    we loop over the list of `must` queries and try to find `geo_distance`

    **The updating is done by reference**

    :param queryset_dict: the queryset in dict format
    :type queryset_dict: dict

    :return:
    """
    query = queryset_dict["query"]

    if "geo_distance" in query:
        queryset_dict["query"] = {"nested": {"path": "location", "query": query}}
    elif "bool" in query and "must" in query["bool"]:
        for index, must_query in enumerate(query["bool"]["must"]):
            if "geo_distance" in must_query:
                queryset_dict["query"]["bool"]["must"][index] = {"nested": {"path": "location", "query": must_query}}

                break

def attach_nested_path_to_sort(queryset_dict: dict):
    """
    This function loops over the `sort` queries, and
    looks for `_geo_distance` in order to add the `nested_path` key/value

    **The updating is done by reference**

    :param queryset_dict: the queryset in dict format
    :type queryset_dict: dict

    :return:
    """
    sort = queryset_dict["sort"]

    if isinstance(sort, list):
        for index, sorting in enumerate(sort):
            if "_geo_distance" in sorting:
                queryset_dict["sort"][index]["_geo_distance"]["nested_path"] = "location"
barseghyanartur commented 4 years ago

Sorry, I'm overwhelmed with work now. I'll check it later (in the weekend).

belthaZornv commented 4 years ago

Thank you @barseghyanartur, I appreciate your reply. :)

belthaZornv commented 4 years ago

If you have some time to give it a look, let me know @barseghyanartur! I understand you're busy though! so thanks for your time dude.