V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
silhouette
V2EX  ›  Django

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

  •  
  •   silhouette · 2018-05-24 22:14:08 +08:00 · 3552 次点击
    这是一个创建于 2157 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我现在有类似这样的需求:用户传入经纬度坐标,对“店面”表(每一个表项都使用 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'

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

    10 条回复    2019-05-18 10:05:20 +08:00
    siteshen
        1
    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
        2
    silhouette  
    OP
       2018-05-25 09:24:02 +08:00 via Android
    @siteshen 您好,很感谢您的回答,但是这样貌似没有办法解决 DRF 无法分页的问题(使用 raw sql 就使用不了)。请问一下有没有什么类似于“ Distance ”的聚集函数能够返回以米为单位的浮点值而不是一个 distance 对象?(因为在调用 DRF 的 cursor pagination 时他必须填写一个“ ordering ”参数,而这个参数就是一个 field 而不能是获取某个 field 的属性,所以我想直接传入一个以米为单位的距离值)
    wph95
        3
    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
        4
    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
        5
    silhouette  
    OP
       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
        6
    silhouette  
    OP
       2018-05-25 18:25:16 +08:00
    @wph95 还有,order_by 对于 distance 来说是可以的,但是他做成 cursor 后从 cursor 里面解包出来放在 sql 里好像要转换成 float 而不能是 distance 对象,所以我在想,有没有一个聚集函数能够直接以 float 形式返回他的距离而不是返回给我一个 distance 对象
    wph95
        7
    wph95  
       2018-05-25 19:03:01 +08:00
    @silhouette
    对这种奇怪的查询没玩过 不确定什么情况
    看你的描述 现在的问题不是在 DRF 上面,而是在 Django orm 上,你的这种复杂的请求让 django orm 没法转换成 sql。你可以用 ipdb 什么的工具断点到那里,把 DRF 拼出来的 queryset 看看是什么样子的,估计就能找到问题原因了,
    silhouette
        8
    silhouette  
    OP
       2018-05-25 20:53:50 +08:00 via Android
    @wph95 已解决,他上传 distance 对象的时候是类似于 “ 1234.44 m ”这样的字符串,我直接把前面那部分内容切出来转换为 float 后再套他给的 distance 的 orm。
    dian7
        9
    dian7  
       2019-05-15 17:56:08 +08:00
    @silhouette,我也要实现一个你这样的功能,请问怎么入手呢 https://lax.v2ex.com/settings
    silhouette
        10
    silhouette  
    OP
       2019-05-18 10:05:20 +08:00 via Android
    @dian7 抱歉,这个问题有一些遥远了,不记得当时具体怎么操作的了。。这个是个外包的东西
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1697 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 16:40 · PVG 00:40 · LAX 09:40 · JFK 12:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.