Django, find max of the sum of 2 fields - python

I have a model Django model like this:
class MyModel(models.Model):
fieldA = models.CharField(max_length=100)
fieldB = models.IntegerField()
fieldC = models.IntegerField()
I want to find the Max(fieldB + fieldC). Is there a Django way to do this? Or do I have to go with raw sql (in case it will be something like "Select Max(fieldA + fieldB) From mymodel_table")?
Thanks in advance

Here is a rather roundabout solution that should work, adapted from Django aggregate queries with expressions:
MyModel.objects.extra(select={'sum':'fieldB + fieldC'}).order_by('-sum')[0]
Original non-working answer (as of Django 1.4, F() expressions do not work in annotations)
You can use F() expressions and annotation/aggregation to do this for you. I think what you want is.
from django.db.models import F, Max
MyModel.objects.annotate(sum=F('fieldB') + F('fieldC')).aggregate(Max('sum'))

Related

How can I join multiple models in Django into a virtual table?

If I have 3 models, like:
class Cow(models.Model):
name =
number_of_eyes =
number_of_feet =
color =
class Pig(models.Model):
name =
number_of_eyes =
number_of_feet =
intelligence =
class Horse(models.Model):
name =
number_of_eyes =
number_of_hooves =
weight_capacity =
speed =
And I'm interested in making a single Livestock table in my template that has instances of all 3, but I'm only interested in these columns that all 3 models have:
name
number_of_eyes
number_of_feet (number_of_hooves if Horse)
And we can ignore all other columns.
How can I join them into a single queryset?
The end goal is to get a single virtual table (queryset) that I can do a few other operations on (filter, order_by, slice), and then return the data in just those columns.
Is this possible in the Django ORM?
I think you have two options:
using itertools.chain:
from itertools import chain
cows = Cow.objects.all()
pigs = Pig.objects.all()
horses = Horse.objects.all()
livestock_list = sorted(
chain(cows, pigs, horses),
key=lambda livestock: livestock.created_at, reverse=True)
)
using contenttypes:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class Livestock(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created']
Now you can query Livestock model like any other model in Django, but you can have a foreign key that can refers to n models. that's what contenttypes do.
Livestock.content_object gives you what you want in your case it can be Cow, Pig or Horse.
Just remember to add objects to Livestock model after you create horse, etc instances. you need to add them in 2 models actually. you can do it with signals.
I think the second solution is better.
Apparently this can also be done using a Union, as suggested by Nick ODell:
from django.db.models import F
Cow.objects.filter(...).union(
Pig.objects.filter(...),
Horse.objects.filter(...).annotate(number_of_feet=F("number_of_hooves"))
).values('name', 'number_of_eyes', 'number_of_feet').order_by('name')[:3]
Unfortunately you can't filter on the resulting queryset after the union, so you need to filter each queryset before the union, but other than that, everything seems to work in my quick test.
From what I understand, the difference here from MojixCoder's suggestion of using ContentType is that you don't need to maintain a separate definition of this virtual table in your Django models module. In some cases, that can be an advantage, as you don't need to keep the module updated when you get new models you want to include in your query, but in other cases, it can be a disadvantage, because my way has a lot of typing every time you want to use this query, whereas in MojixCoder's example, you define it once, and your queries would be much shorter.
Edit: Using annotate and union can result in the results being out of order. Special care must be taken to ensure this doesn't happen

Get time based model statistics in django

I know this is not a django question per say but I am working with django models and would like to have a solution specific to django
Suppose I have a model like this
class Foo(models.Model):
type = models.IntegerField()
timestamp = models.DateTimeField(auto_now_add=True)
Now what is the best method get a count of all objects of type(say 1) spread over date/time
For example: get_stat(type=1) gives me information on how many objects(of type 1) were created on 12/10/2018, on 13/10/2018, 14/10/2018 and so on...
I think you need to use group by. See this answer: How to query as GROUP BY in django?
#classmethod
def get_stat(cls, type):
return cls.objects.filter(type=type).values('timestamp').annotate(
count=Count('id')
).values('timestamp', 'count')
This function is an example in your case.

Django aggregate filters

I have 3 models similar to the below, and I am trying to get the latest sale date for my items in a single query, which is definitely possible using SQL, but I am trying to use the built in Django functionality:
class Item(models.Model):
name = models.CharField()
...
class InventoryEntry(models.Model):
delta = models.IntegerField()
item = models.ForeignKey("Item")
receipt = models.ForeignKey("Receipt", null=True)
created = models.DateTimeField(default=timezone.now)
...
class Receipt(models.Model):
amt = models.IntegerField()
...
What I am trying to do is query my items and annotate the last time a sale was made on them. The InventoryEntry model can be queried for whether or not an entry was a sale based on the existence of a receipt (inventory can also be adjusted because of an order, or being stolen, etc, and I am only interested in the most recent sale).
My query right now looks something like this, which currently just gets the latest of ANY inventory entry. I want to filter the annotation to only return the max value of created when receipt__isnull=False on the InventoryEntry:
Item.objects.filter(**item_qs_kwargs).annotate(latest_sale_date=Max('inventoryentry_set__created'))
I attempted to use the When query expression but it did not work as intended, so perhaps I misused it. Any insight would be appreciated
A solution with conditional expressions should work like this:
from django.db.models import Max, Case, When, F
sale_date = Case(When(
inventoryentry__receipt=None,
then=None
), default=F('inventoryentry__created'))
qs = Item.objects.annotate(latest_sale_date=Max(sale_date))
I have tried some modified solution. Have a look.
from django.db.models import F
Item.objects\
.annotate(latest_inventoryentry_id=Max('inventoryentry__created'))\
.filter(
inventoryentry__id=F('latest_inventoryentry_id'),
inventoryentry__receipt=None
)
I did not check manually. you can check and let me know.
Thanks

Using Django, how I can achieve this in a single query?

I have the following model:
class Ticket(models.Model):
# ... other fields omitted
active_at = models.DateTimeField()
duration = models.DurationField()
Given now = datetime.now(), I'd like to retrieve all records for which now is between active_at and active_at + duration.
I'm using Django 1.8. Here are the DurationField docs.
As noted in the documentation, arithmetic with a DurationField will not always work as expected in databases other than PostgreSQL. I don't know to which extend this works or doesn't work in other databases, so you'll have to try it yourself.
If that works, you can use the following query:
from django.db.models import F
active_tickets = Ticket.objects.filter(active_at__lte=now,
active_at__gt=now-F('duration'))
The F object refers to a field in the database, duration in this case.

Django Many-To-Many filtering

Assume the following set of models:
class A(models.Model):
pass
class B(models.Model):
pass
class M2M(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)
A way to filter (this is a part in the chain of a larger application) by some conditions on the links, in naive Django ORM is to do this:
def fun():
as = A.objects.filter("some complex queryset")
m2ms = M2M.objects.filter("some complex B-dependent QS")
return as.filter(id__in=[m.a_id for m in m2ms])
But obviously this produces a rather awful query "id__in", and clearly executes as two queries.
Is there a better way to get Django to produce a proper join?
You should explicitly declare a many-to-many field from A to B through M2M.
class A(models.Model):
bs = models.ManyToManyField('B', through='M2M')
Now you can simply do:
A.objects.filter(condition_on_A='foo').filter(b__condition_on_b='bar')
You would be able to achieve this in a single query. For example, lets say you want to filter only those records which have a value greater than 50 on field x in the model B, you would do:
A.objects.filter("some-filter-criteria", m2m__b__x__gt=50)
You can read more on related name lookups here

Categories

Resources