我现在有类似这样的需求:用户传入经纬度坐标,对“店面”表(每一个表项都使用 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'
请问一下站友们有没有碰到这种情况?或者说这种情况应该如何解决(或者是有别的分页策略)
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)` |
2
silhouette OP @siteshen 您好,很感谢您的回答,但是这样貌似没有办法解决 DRF 无法分页的问题(使用 raw sql 就使用不了)。请问一下有没有什么类似于“ Distance ”的聚集函数能够返回以米为单位的浮点值而不是一个 distance 对象?(因为在调用 DRF 的 cursor pagination 时他必须填写一个“ ordering ”参数,而这个参数就是一个 field 而不能是获取某个 field 的属性,所以我想直接传入一个以米为单位的距离值)
|
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 了。 |
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 |
5
silhouette OP @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)))这样的查找,我感觉我构造的并没有出现错误呀。。 请问这里是什么原因呢?谢谢。 |
6
silhouette OP @wph95 还有,order_by 对于 distance 来说是可以的,但是他做成 cursor 后从 cursor 里面解包出来放在 sql 里好像要转换成 float 而不能是 distance 对象,所以我在想,有没有一个聚集函数能够直接以 float 形式返回他的距离而不是返回给我一个 distance 对象
|
7
wph95 2018-05-25 19:03:01 +08:00
@silhouette
对这种奇怪的查询没玩过 不确定什么情况 看你的描述 现在的问题不是在 DRF 上面,而是在 Django orm 上,你的这种复杂的请求让 django orm 没法转换成 sql。你可以用 ipdb 什么的工具断点到那里,把 DRF 拼出来的 queryset 看看是什么样子的,估计就能找到问题原因了, |
8
silhouette OP @wph95 已解决,他上传 distance 对象的时候是类似于 “ 1234.44 m ”这样的字符串,我直接把前面那部分内容切出来转换为 float 后再套他给的 distance 的 orm。
|
9
dian7 2019-05-15 17:56:08 +08:00
@silhouette,我也要实现一个你这样的功能,请问怎么入手呢 https://lax.v2ex.com/settings
|
10
silhouette OP @dian7 抱歉,这个问题有一些遥远了,不记得当时具体怎么操作的了。。这个是个外包的东西
|