问题
I have 2 model in django a zone and a shop, models are like this:
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 is a PointField with a better map in django admin.
I want on every shop saving select zone automatically base on shop location , zone location and radius. If there is no zone with radius to support shop it will be None. I tried this query:
zone_list = Zone.objects.filter(
location_point__distance_lte=(
shop.location_point, D(m=models.F('radius'))
)
)
But I get this error:
TypeError: float() argument must be a string or a number, not 'F'
How can I fix this?
回答1:
That seems to happen inside the MeasureBase
class of django.contrib.gis.measure
(which Distance/D inherits from) and more specifically in the default_units method where it tries to cast str
or numeric input values to float
but receives an F
expression instead.
What we can do as a workaround, is to annotate
the Distance (careful with this Distance
method because it comes from the GeoDjango Geographic Database Functions) between the shop.location_point
and the current location_point
and then we can filter by that distance being <=
than the instance 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'))
Kudos to this excellent answer from @e4c5: GeoDjango filter by distance from a model field
Another approach would be to eliminate the annotation
part entirely and go straight to the filtering by Distance
:
from django.contrib.gis.db.models.functions import Distance
zone_list = Zone.objects.filter(
radius_gte=Distance('location_point', shop.location_point)
)
I leave this here for comment continuity:
You can try to cast the F('radius')
result as a FloatField()
using the Cast() method to turn the Integer to a Float.
zone_list = Zone.objects.filter(
location_point__distance_lte=(
shop.location_point,
D(m=Cast('radius', output_field=models.FloatField()))
)
)
来源:https://stackoverflow.com/questions/58308732/django-filter-location-distance-with-dynamic-radius