django rest framework 的 CursorPagination 如何按照距离(GeoDjango 的 Distance)进行排序? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
silhouette
V2EX    Django

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

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

    我现在有类似这样的需求:用户传入经纬度坐标,对“店面”表(每一个表项都使用 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://githubcom/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     Solana     786 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 21:23 PVG 05:23 LAX 13:23 JFK 16:23
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86