Django model field storing a function or class - django

Is it possible to create a Django Custom Model Field that references a function or class, instead of a model?
In case you're wondering why i would want this, here's a brief explanation of waht i'm trying to achieve.
Basically i have a FreightTable model, that is used to calculate the value of a freight, so it should have method to do this. But the problem is that there are several different ways to calculate it, and each instance of the FreightTable should calculate on its specific way.
At first i thought about solving this using some kind of polymorphism, but then i would have to create an specific model for each different algorithm, and they would also be in different tables in the DB, what would be a problem for me. I also thought about using Django Polymorphic, but i heard it doesnt really scale well, so it's also not a good idea.
My thought is that if I could just reference this different algorithms on a Model Field, I'll have an ellegant and eficient solution.

My thought is that if I could just reference this different algorithms
on a Model Field
This is a good idea, for example:
CALCULATION_TYPES = [(1, 'Normal'), (2, 'Express')]
class FreightTable(models.Model):
# Normal fields
calculation_type = models.IntegerField(choices=CALCULATION_TYPES)
def calc_normal(self):
pass
def calc_express(self):
pass
def calc_default(self):
pass
Now, for each freight type, you set your calculation method:
ft = FreightType(calculation_type=2)
ft.save()
At the point where you want to display the result of the calculation, fetch the method from the instance, and then call the appropriate method:
call_map = {1: 'calc_normal', 2: 'calc_express'}
ft = FreightTable.objects.get(pk=1)
calculated_value = getattr(ft, call_map(ft.calculation_type))()

Python classes, functions and methods cannot be pickled, so you cannot store the code itself in the database.
What you can do is
1) Store the full dotted path to the function in a CharField. Then resolve the reference to function using zope.dottedname package, and call it.
or
2) Store the calculation code as Python source code in the database as plain text. Then execute it through eval() or dynamic module import using imp module
I am not sure if Django had internal dotted name resolver, you could use it instead of zope.dottedname.

Related

How could I translate a spatial JOIN into Django query language?

Context
I have two tables app_area and app_point that are not related in any way (no Foreign Keys) expect each has a geometry field so we could spatially query them, respectively of polygon and point type. The bare model looks like:
from django.contrib.gis.db import models
class Point(models.Model):
# ...
geom = models.PointField(srid=4326)
class Area(models.Model):
# ...
geom = models.PolygonField(srid=4326)
I would like to create a query which filters out points that are not contained in polygon.
If I had to write it with a Postgis/SQL statement to perform this task I would issue this kind of query:
SELECT
P.*
FROM
app_area AS A JOIN app_point AS P ON ST_Contains(A.geom, P.geom);
Which is simple and efficient when spatial indices are defined.
My concern is to write this query without hard coded SQL in my Django application. Therefore, I would like to delegate it to the ORM using the classical Django query syntax.
Issue
I could not find a clear example of this kind of query on the internet, solutions I have found:
Either rely on predefined relation using ForeignKeyField or prefetch_related (but this relation does not exist in my case);
Or use a single hand crafted geometry to represent the polygon (but this is not my use case as I want to rely on another table as the polygon source).
I have the feeling that is definitely achievable with Django but maybe I am too new to this framework or it is not enough documented or I have not found the rightful keywords set to google it.
The best I could find in the official documentation is the FilteredRelation object which seems to do what I want: defining the ON part of the JOIN clause, but I could not setup properly, mainly I don't understand how to fill the other table and point to proper fields.
from django.db.models import F, Q, FilteredRelation
query = Location.objects.annotate(
campus=FilteredRelation(<relation_name>, condition=Q(geom__contains=F("geom")))
)
Mainly the field relation_name puzzle me. I would expect it to be the table I would join (here Area) on but it seems it is a column name which is expected.
django.core.exceptions.FieldError: Cannot resolve keyword 'Area' into field. Choices are: created, geom, id, ...
But this list of fields are from Point table.
My question is: How could I translate my spatial JOIN into Django query language?
Nota: There is no requirement to rely on FilteredRelation object, this is just the best match I have found for now!
Update
I am able to emulate the expected output using extra:
results = models.Point.objects.extra(
where=["ST_intersects(app_area.geom, app_point.geom)"],
tables=["app_area"]
)
Which returns a QuerySet but it still needs to inject plain SQL statement and then the generated SQL are not equivalent in term of clauses:
SELECT "app_point"."id", "app_point"."geom"::bytea
FROM "app_point", "app_area"
WHERE (ST_intersects(app_area.geom, app_point.geom))
And EXPLAIN performances.
I think, the best solution would be to aggregate the areas and then do an intersection with the points.
from django.db.models import Q
from django.contrib.gis.db.models.aggregates import Union
multipolygon_area = Area.objects.aggregate(area=Union("geom"))["area"]
# Get all points inside areas
Points.objects.filter(geom__intersects=multipolygon_area)
# Get all points outside areas
Points.objects.filter(~Q(geom__intersects=multipolygon_area))
This is quite efficient as it is completely calculated on the database level.
The idea was found here

Django ORM join with another table

I have a table known as messages. In my application, users can send different type of messages. Like forwarding an event, etc. As such, I have columns type and value in that table.
What I want to do is for a particular type, goto a particular table and make sure the value is valid (typically this maps to the id of that table). There could be multiple types, and each one has to be mapped to a different table. Is there a way to logically write this in the built in django ORM? Right now I'm only seeing this feasible if I use straight SQL, which I would rather not if I can get away with it...
Right now I'm doing something like:
Messages.objects.all().filter(Q(user_id=id))...etc
I want to add to the statement above checking the type and for the particular type, check the table associated with it.
It sounds like you have a "polymorphic association". There are a couple ways to do analogous things in Django, but I think the one that most closely matches what you described is the contenttypes module, which uses separate columns for the type and for the value as in your application.
You may just want to define a multi-table inheritance structure. This gets you the same result in that you have multiple types of messages and field inheritance, but you don't have to mess with a type field at all.
class Message(models.Model):
value = models.CharField(max_length=9000)
class Event(Message):
pass
class Tweet(Message):
pass
In view post handler:
...
if request.POST['type'] == 'event':
Event.objects.create(value=request.POST['value'])
elif request.POST['type'] == 'tweet':
Tweet.objects.create(value=request.POST['value'])
...
Then, get your objects:
all_tweets = Tweet.objects.all()
all_messages = Message.objects.all()
for message in all_messages:
try:
event = message.event
except Event.DoesNotExist:
# this message is not an Event
pass
If this sort of structure doesn't work for you, there are two other styles of Model inheritance supported by Django: Abstract base classes and Proxy models. You could also hold a GenericForeignKey as suggested by #AndrewGorcester.

Django model inheritance vs composition, and querying multiple models/tables together

I have a Django app that has a number of different models, all with a bunch of common data. Ultimately, I think this question comes down to a decision between inheritance and composition. My current implementation is something like this:
class Thing(models.Model):
foo = models.FooField()
bar = models.BarField()
type = models.CharField()
class A(CommonStuff):
aFoo = models.FooField()
class B(CommonStuff):
bFoo = models.FooField()
With this model, I'm able to query for a Thing using the Thing model's manager. Using the type field on Thing, I can get the child object data by looking at the type field, which contains either 'a' or 'b', and then asking for (i.e.) thing.a.aFoo. This is a feature I like because it's a fairly common operation in my app to get a list of all Thing objects.
I see a couple couple issues here. First, the type field seems unnecessary. Is there way to get at the child data without having to first look up the type? It seems like I could work around this with an instance method that returned the correct object given its type value, or if I really wanted to get rid of the type field, I could iterate over each of the reverse relation fields on Thing, looking for one that doesn't raise a DoesNotExist exception. This feels quite brittle to me though. If I add a new 'subthing' C, I have to update Thing to look for the new type. I could fix this by making Thing and abstract model. That way, A and B get all the fields of Thing and I avoid having to use the type field. Problem, though, is that I lose the ability to perform queries for all Thing objects.
Another model I'm thinking about sort of flips this one on its head by turning the data in Thing into a field on A and B.
class Thing(models.Model):
foo = models.FooField()
bar = models.BarField()
class A(models.Model):
aFoo = models.FooField()
thing = models.OneToOneField(Thing)
class B(models.Model):
bFoo = models.FooField()
thing = models.OneToOneField(Thing)
This version has a few benefits. It gets rid of the type field on Thing, and—at least to me—looks and feels cleaner and less brittle. The problem here, though, is the same as the problem with making Thing abstract in the first version. I lose the ability to query all my 'subthings' together. I can do a query for A objects or a query for B objects, but not both. Can use this version of the model without having to sacrifice the ability to query for all 'subthings'? One possibility is to write a manager that queries both models and returns a QuerySet of all the objects. Does that work?

Passing limited queryset to related_to() in django-tagging

I want to use the related_to() function in django-tagging and I am passing a queryset looking like this:
Chapter.objects.all().order_by('?')[:5] #the important thing here is the "[:5]"
My problem is that this function apparently uses the in_bunk() function and you Cannot use 'limit' or 'offset' with in_bulk
How can I restrict my queryset to only pass 5 objects and at the same time make use of in_bunk?
I know that related_to() lets you pass the variable num (which is the number of objects it should return) but I don't want it to output the same queryset every single time. So i came up with the idea of ordering it randomly and limiting it before it was passed to the function. But as you can see: limited querysets and bunk_it doesn't go hand in hand very well.
I found a solution though it wasn't the best and though it processes unnecessary data. I simply run through all instances of the model related to the current instance and I then sort randomly and slice afterwards:
related_objects = instance.related_to(Model) # all related objects are found
related_objects = random.sample(related_objects,5) # .. and afterwards sorted randomly and sliced

Django - How to annotate QuerySet using multiple field values?

I have a model called "Story" that has two integer fields called "views" and "votes". When I retrieve all the Story objects I would like to annotate the returned QuerySet with a "ranking" field that is simply "views"/"votes". Then I would like to sort the QuerySet by "ranking". Something along the lines of...
Story.objects.annotate( ranking=CalcRanking('views','votes') ).sort_by(ranking)
How can I do this in Django? Or should it be done after the QuerySet is retrieved in Python (like creating a list that contains the ranking for each object in the QuerySet)?
Thanks!
PS: In my actual program, the ranking calculation isn't as simple as above and depends on other filters to the initial QuerySet, so I can't store it as another field in the Story model.
In Django, the things you can pass to annotate (and aggregate) must be subclasses of django.db.models.aggregates.Aggregate. You can't just pass arbitrary Python objects to it, since the aggregation/annotation actually happens inside the database (that's the whole point of aggregate and annotate). Note that writing custom aggregations is not supported in Django (there is no documentation for it). All information available on it is this minimal source code: https://code.djangoproject.com/browser/django/trunk/django/db/models/aggregates.py
This means you either have to store the calculations in the database somehow, figure out how the aggregation API works or use raw sql (raw method on the Manager) to do what you do.