Расстояние до фильтра Джанго с динамическим радиусом - PullRequest
1 голос
/ 09 октября 2019

У меня есть 2 модели в django зоне и магазине, модели такие:

from django.contrib.gis.db import models
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D
from location_field.models.spatial import LocationField


class Zone(models.Model):
    name = models.CharField(max_length=200)
    location_point = LocationField(based_fields=['city'], zoom=7, default=Point(51.67, 32.65))
    radius = models.IntegerField(default=1000)  # radius in meters

class Shop(models.Model):
    name = models.CharField(max_length=200)
    location_point = LocationField(based_fields=['city'], zoom=7, default=Point(51.67, 32.65), null=True, blank=True)
    zone = models.ForeignKey(Zone, on_delete=models.CASCADE, null=True)

LocationField - это PointField с лучшей картой в админке django.

Я хочу, чтобы при каждом сохранении магазина автоматически выбиралась зона в зависимости от местоположения магазина, расположения зоны и радиуса. Если нет зоны с радиусом для поддержки магазина, это будет Нет. Я пробовал этот запрос:

zone_list = Zone.objects.filter(
    location_point__distance_lte=(
        shop.location_point, D(m=models.F('radius'))
    )
)

Но я получаю эту ошибку:

TypeError: аргумент float () должен быть строкой или числом, а не 'F'

Как я могу это исправить?

1 Ответ

1 голос
/ 10 октября 2019

Очевидно, что предыдущий подход не работает !!

Это, кажется, происходит внутри MeasureBase класса django.contrib.gis.measure (который Distance / D наследуется от) и, более конкретно, в методе default_units , где он пытается преобразовать str или числовые входные значения в float, но вместо этого получает выражение F.

То, что мы можем сделать в качестве обходного пути, это annotate Distance ( осторожно с этим Distance методом, потому что он исходит из функций GeoDjango для географической базы данных ) между shop.location_point и текущим location_point, и тогда мы можем отфильтровать это расстояние на <=, чем экземпляр radius:

from django.contrib.gis.db.models.functions import Distance

zone_list = Zone.objects.annotate(
    distance=Distance('location_point', shop.location_point)
).filter(distance__lte=F('radius'))

Престижность этому превосходному ответуот @ e4c5: Фильтр GeoDjango по расстоянию от модельного поля

Другой подход заключается в том, чтобы полностью исключить часть annotation и перейти к фильтрации по Distance:

from django.contrib.gis.db.models.functions import Distance

zone_list = Zone.objects.filter(
    radius_gte=Distance('location_point', shop.location_point)
)


Я оставляю это здесь для непрерывности комментариев:

Вы можете попытаться привести результат F('radius') как FloatField(), используя метод Cast(), чтобы превратить целое число в число с плавающей точкой.

zone_list = Zone.objects.filter(
    location_point__distance_lte=(
        shop.location_point, 
        D(m=Cast('radius', output_field=models.FloatField()))
    )
)

...