繁体   English   中英

DRF:如何创建自定义 FilterSet 以按距离过滤最近的用户

[英]DRF: how to create custom FilterSet to filter nearest users by distance

我正在尝试创建自定义FilterSet以使用django-filter按距离过滤附近的用户 例如,如果我发送
GET /api/list/?distance=300 ,我想获取附近所有低于或等于300m远的用户

我的 model 有 2 个字段:

latitude = models.DecimalField(  # [-90.000000, 90.000000]
    max_digits=8,
    decimal_places=6,
    null=True
)
longitude = models.DecimalField(  # [-180.000000, 180.000000]
    max_digits=9,
    decimal_places=6,
    null=True
)

objects = ClientManager()

我的ClientManager有 function 用于从 model 获取坐标:

def get_geo_coordinates(self, pk):
    """
    :param pk: - client id
    :return: client's coords
    """

    instance = self.get(pk=pk)
    data = (instance.latitude, instance.longitude)
    return data

我的GetListAPIView

class GetClientListAPIView(ListAPIView):

    """
        Returns list with filtering capability
        Available filter fields:
            gender, first_name, last_name, distance
    """

    serializer_class = ClientSerializer
    queryset = Client.objects.all()
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend]
    filter_class = ClientFilter

我的ClientFilter

class ClientFilter(FilterSet):

    distance = filters.NumberFilter(method='get_nearest_clients')

    def get_nearest_clients(self, queryset, name, value):
        sender_coords = Client.objects.get_geo_coordinates(pk=self.request.user.id)
        test_coords = Client.objects.get_geo_coordinates(pk=31)
        dist = get_great_circle_distance(sender_coords, test_coords)

    class Meta:
        model = Client
        fields = ['gender', 'first_name', 'last_name']

在这里,我使用我的 function 来计算两个客户端之间的距离:

def get_great_circle_distance(first_coords, second_coords):
    """
        :param first_coords: (first_client_latitude, first_client_longitude) in degrees
        :param second_coords: (second_client_latitude, second_client_longitude) in degrees
        :return: distance
    """

    earth_radius = 6_400_000  # in metres

    la_1, lo_1 = map(radians, first_coords)
    la_2, lo_2 = map(radians, second_coords)

    coefficient = acos(
        cos(la_1) * cos(la_2) * cos(lo_1 - lo_2) +
        sin(la_1) * sin(la_2)
    )
    distance = earth_radius * coefficient

    return distance

我不知道如何过滤查询queryset并从数据库访问端优化它。

我建议使用很久以前解决这个问题的现有工具。 在不规则形状的球体上进行准确(且有效)的距离计算比这更复杂。

https://docs.djangoproject.com/en/4.0/ref/contrib/gis/install/postgis/

model上的GIS字段:

from django.contrib.gis.db.models import PointField

class Client(models.Model):
    location = PointField()

这为您提供了直接在查询集上进行距离计算的适当工具,并且计算是在数据库端 afaik 完成的。( https://docs.djangoproject.com/en/4.0/ref/contrib/gis/tutorial/#spatial -查询

正确设置 GIS 的开销要多一些,但值得付出努力。

旁注:可以通过 queryset annotate()QF表达式手动完成,但正如我所说,要做到这一点很棘手。 正如您在那里尝试的那样,在客户端过滤django-filter违背了首先使用django-filter的目的。 希望这有助于更好地理解。

我解决了这个问题

class ClientFilter(FilterSet):
"""
     Custom ClientFilter
"""

distance = filters.NumberFilter(method='get_nearest_clients')

def get_nearest_clients(self, queryset: QuerySet, name: str, dist_value: int):
    sender_id = self.request.user.id
    sender_la, sender_lo = map(radians, Client.objects.get_geo_coordinates(pk=sender_id))
    earth_radius = 6_400_000

    queryset = (
        queryset.exclude(pk=sender_id)
        .alias(
            rad_lat=Radians('latitude'),
            rad_long=Radians('longitude'),
            distance=ExpressionWrapper(
                ACos(
                    Cos('rad_lat') * Cos(sender_la) * Cos(F('rad_long') - sender_lo)
                    + Sin('rad_lat') * Sin(sender_la)
                ) * earth_radius,
                output_field=FloatField()
            )
        )
        .exclude(distance__gte=dist_value)
        .order_by('distance')
    )

    return queryset

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM