django rest framework 的 CursorPagination 如何按照距离(GeoDjango 的 Distance)进行排序?

2018-05-24 22:14:08 +08:00
 silhouette

我现在有类似这样的需求:用户传入经纬度坐标,对“店面”表(每一个表项都使用 GeoDjango 的 PointField 存储经纬度)进行排序(用户的距离由近到远排序),并且使用 DRF 的 CursorPagination 进行分页加载。 问题出在分页加载时,如果传入了 cursor 参数就会报错误。代码如下: views.py:

class BranchesView(ListAPIView): authentication_classes = (MemberAuthentication,)

serializer_class = serializers.BranchesSerializer
pagination_class = BranchesCursorPagination

def get_queryset(self):
    if self.request.user.member_of:
        raise exceptions.RequestFailed
    try:
        latitude = float(self.request.query_params['latitude'])
        longitude = float(self.request.query_params['longitude'])
        assert 90.00 >= latitude >= -90.00
        assert 180.00 >= longitude >= -180.00
        user_coordinates = Point(longitude, latitude, srid=4326)
    except:
        raise exceptions.ArgumentError
    return Branch.objects.get_existing_all().annotate(distance=Distance('location__coordinates', user_coordinates))

分页器:

class BranchesCursorPagination(CursorPagination): page_size = 20 ordering = 'distance'

def get_paginated_response(self, data):
    #这个是为了改变输出的格式
    response = {
        'data': {
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link(),
            },
            'branches': data,
        },
        'code': 0,
        'error': '',
    }
    return Response(response)

然后访问带有 cursor 参数的链接时返回了以下错误: could not convert string to float: '1190493.2321520457 m'

请问一下站友们有没有碰到这种情况?或者说这种情况应该如何解决(或者是有别的分页策略)

3575 次点击
所在节点    Django
10 条回复
siteshen
2018-05-25 00:47:23 +08:00
1. 写出能按距离排序的 SQL 语句;
2. 翻译成对应的 python 代码。

不考虑性能的话:
1. sql_let ordering = ((long - ${x})^2 + (lat - ${y})^2)
2. select * from branch order by ordering where ordering < {distance} desc
3. Branch.objects.filter(RAW_SQL('((long - %s)^2 + (lat - %s)^2) < %s') % (x, y, distance))

另外不知道你生成的 SQL 是怎么样的?看样子是把 `Distance()` 直接当成字符串直接传入到 SQL 语句了。这里需要知道 Distance('location__coordinates', user_coordinates) 对应的 SQL 语句类型(比如是 float ?),然后传入值时也变成对应类型,大约类似这样: `objects.filter(Distance('location__coordinates', user_coordinates) < distance.metres)`
silhouette
2018-05-25 09:24:02 +08:00
@siteshen 您好,很感谢您的回答,但是这样貌似没有办法解决 DRF 无法分页的问题(使用 raw sql 就使用不了)。请问一下有没有什么类似于“ Distance ”的聚集函数能够返回以米为单位的浮点值而不是一个 distance 对象?(因为在调用 DRF 的 cursor pagination 时他必须填写一个“ ordering ”参数,而这个参数就是一个 field 而不能是获取某个 field 的属性,所以我想直接传入一个以米为单位的距离值)
wph95
2018-05-25 15:44:44 +08:00
强答一下,问题有些没看懂,我就按我理解来回答了。
ordering = 'distance' 但是这个 distance 并不是 sql 返回的字段。

那么这种定制化高的情况,我就不用 CursorPagination
为什么呢, 因为
https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py#L517
CursorPagination 的 ordering 会调用 django orm 的 ordering, 如果你的 distance 在 django orm 不可用 那就 gg 了。
wph95
2018-05-25 15:46:16 +08:00
要我就自己写一个 Pagination
主要是 base on PageNumberPagination
修改 https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py#L189 这个 method
silhouette
2018-05-25 18:22:58 +08:00
@wph95 您好,我尝试了自己重载 CursorPagination 里面的 paginate_queryset 方法,就是把他自带的
kwargs = {order_attr + '__lt': current_position}
改写成带有距离的查询
kwargs = {order_attr + '__distance_lte': current_position}

但是这样仍然会报错,报的是“ Unsupported lookup 'distance_gte' for DistanceField or join on the field not permitted.”
我想知道为什么不允许这种查询方式呢?
ps:官方给出了 Zipcode.objects.filter(poly__distance_gte=(geom, D(m=5)))这样的查找,我感觉我构造的并没有出现错误呀。。
请问这里是什么原因呢?谢谢。
silhouette
2018-05-25 18:25:16 +08:00
@wph95 还有,order_by 对于 distance 来说是可以的,但是他做成 cursor 后从 cursor 里面解包出来放在 sql 里好像要转换成 float 而不能是 distance 对象,所以我在想,有没有一个聚集函数能够直接以 float 形式返回他的距离而不是返回给我一个 distance 对象
wph95
2018-05-25 19:03:01 +08:00
@silhouette
对这种奇怪的查询没玩过 不确定什么情况
看你的描述 现在的问题不是在 DRF 上面,而是在 Django orm 上,你的这种复杂的请求让 django orm 没法转换成 sql。你可以用 ipdb 什么的工具断点到那里,把 DRF 拼出来的 queryset 看看是什么样子的,估计就能找到问题原因了,
silhouette
2018-05-25 20:53:50 +08:00
@wph95 已解决,他上传 distance 对象的时候是类似于 “ 1234.44 m ”这样的字符串,我直接把前面那部分内容切出来转换为 float 后再套他给的 distance 的 orm。
dian7
2019-05-15 17:56:08 +08:00
@silhouette,我也要实现一个你这样的功能,请问怎么入手呢 https://lax.v2ex.com/settings
silhouette
2019-05-18 10:05:20 +08:00
@dian7 抱歉,这个问题有一些遥远了,不记得当时具体怎么操作的了。。这个是个外包的东西

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/457570

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX