2

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?

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
mastisa
  • 1,875
  • 3
  • 21
  • 39

1 Answers1

3

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

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
  • 1
    I checked your answer but it didn't worked! TypeError: float() argument must be a string or a number, not 'Cast'. – mastisa Oct 10 '19 at 21:33
  • @Mastisa I hadn't tested that, my bad. Is it possibe that some of you `radius` is set to `None` and not as an integer? – John Moutafis Oct 10 '19 at 21:58
  • No it always have an integer. I think problem is with D method, it work with an int like 1000 but didn't accept methods. is it related? – mastisa Oct 10 '19 at 22:06
  • @Mastisa Maybe. Can you try the following: `D(m=models.F('radius')*Decimal(1.0))` and let me know what happens? – John Moutafis Oct 10 '19 at 22:12
  • 1
    TypeError: float() argument must be a string or a number, not 'CombinedExpression' – mastisa Oct 10 '19 at 22:35
  • 1
    @Mastisa I have edited the answer with a workaround. Have a look. – John Moutafis Oct 10 '19 at 23:10
  • 1
    Thanks for your help, I had a solution like this but definitely your solution is better than mine. I think the real answer for this question is a way to convert argument of D method to float. – mastisa Oct 11 '19 at 07:20
  • 1
    @Mastisa I researched the problem a bit and I didn't find a way to convert the `D` to float. But I composed a more straightforward way to filter without the extra step of the annotation, which I added to the answer. – John Moutafis Oct 14 '19 at 10:01