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

发布于 2025-01-17 14:22:06 字数 2430 浏览 3 评论 0原文

我正在尝试使用django-filter来创建用于通过距离过滤附近用户过滤附近用户的自定义 例如,如果我发送
想让所有附近的用户 等于

get/api/list/?dance = 300,我

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()

300m > clientmanager 具有从模型中获取坐标的功能:

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']

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

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,并从数据库访问侧面进行最佳操作。

I'm trying to create custom FilterSet for filtering nearby users by distance using django-filter
For example if I send
GET /api/list/?distance=300, I want to get all nearby users who are lower or equals to 300m far away

My model has 2 fields:

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()

My ClientManager has function for getting coords from 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

My 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

My 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']

Here I'm using my function for calculating distance between two clients:

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

I do not know how to filter the queryset and do it optimally from the database accesses side.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

小嗲 2025-01-24 14:22:06

I would recommend using the existing tools that solved this problem a long time ago. Doing accurate (and efficient) distance calculations on an irregularly shaped sphere is more complicated than this.

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

gis字段上的gis字段:

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

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

这为您提供了直接在querySet上进行距离计算的正确工具,并且在数据库侧AFAIK上进行了计算。( https://djangoproject.com/44.0/ref /contrib/gis/tutorial/#spatial-queries)

It is a bit more overhead to setup GIS properly but it's worth the effort.

Sidenote:可以通过QuerySet Annotate()qf表达方式手工做。过滤django-filter在您尝试的情况下,在客户端上过滤,几乎会失败的目的是首先使用django-filter。希望有助于获得更好的理解。

I would recommend using the existing tools that solved this problem a long time ago. Doing accurate (and efficient) distance calculations on an irregularly shaped sphere is more complicated than this.

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

GIS field on model:

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

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

This gives you the proper tools to do distance calculations directly on the queryset and the computation is done on the database side afaik.(https://docs.djangoproject.com/en/4.0/ref/contrib/gis/tutorial/#spatial-queries)

It is a bit more overhead to setup GIS properly but it's worth the effort.

Sidenote: It is possible to do it by hand via queryset annotate() and Q and F expressions but as I said it's tricky to get right. Filtering django-filter on the client-side, as you attempted there, pretty much defeats the purpose of using django-filter in the first place. Hope that helps getting a better understanding.

み格子的夏天 2025-01-24 14:22:06

我解决了这个问题

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

I fixed this issue

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文