How to do arthmetic on Django 'F' types?

Viewed 365

I have an Event model, that, for the sake of simplicity only has a lat and lon FloatField().

From pythons native "geopy.distance" library, you can acquire the distance bewteen two coordinates like so:

from geopy.distance import distance
home = (33.28473, -117.20384)
event = (33.829384, -117.38932)
distance = distance(home, event).miles  #returns a float 

In my application context I need to order all of the events by location. I attempted standard django annotation syntax:

Event.objects.all().annotate(distance=distance( (F('lat'),F('lon')), home ).miles).order_by('distance')

But I get the following error from the geopy library:

TypeError: float() argument must be a string or a number, not 'F'

Strangely enough, this works:

>>> def foo(x):
        return x * 2
>>> Event.objects.all().annotate(cutomField=foo(F('field')))

It seems that you can do arthmetic with F types but geopy does not seem to know this.

Is there anyway to turn an F type into a float so that geopy can process the field?

1 Answers

Django supports negation, addition, subtraction, multiplication, division, modulo arithmetic, and the power operator on query expressions, using Python constants, variables, and even other expressions.

An F() object represents the value of a model field or annotated column. It makes it possible to refer to model field values and perform database operations using them without actually having to pull them out of the database into Python memory.

Instead, Django uses the F() object to generate an SQL expression that describes the required operation at the database level.

Above details are from django official documentation link

for eg 1.

def foo(x):
    return x*2

qs = MyModel.objects.filter().annotate(res=foo(F('colN')))
print(qs.query)

output:

SELECT col1, col2, ("model_table"."colN" * 2) AS "res" FROM "model_table"

eg 2(considering another function).

def foo_power(x):
    return x**2 ## POWER of 2

qs = MyModel.objects.filter().annotate(res=foo_power(F('colN')))
print(qs.query)

Output: SELECT col1, col2, (POWER("music_movie"."title",2)) AS "res" FROM "model_table"

Which shows calculation happens on database-side:

for geopy.distance case:

For custom set-calculation we need to write own Func, with the help of django built-in database functions. can be followed https://docs.djangoproject.com/en/3.0/ref/models/expressions/#func-expressions

Is there anyway to turn an F type into a float so that geopy can process the field?

No, you can not type cast F() i.e. <class 'django.db.models.expressions.F'> to float.

Because:

def foo_power(x):
    print(type(x), x)
    return x**2 ## POWER of 2

qs = MyModel.objects.filter().annotate(res=foo_power(F('colN')))

Output: <class 'django.db.models.expressions.F'> F(title)

Related