Calculating fields with filters in Django - python

I am extremely new to Django and kinda new to python and programming in general, so i may be asking a dumb question. I am trying to track medications in our inventory at work. Here is my model for the inventory in our safe
class Safe(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
date_created = models.DateTimeField(auto_now=True)
drug_name = models.ForeignKey('components.Drug', related_name='drug_remove', on_delete=models.PROTECT, default=0)
amount_removed = models.IntegerField(blank=True, null=True)
amount_added = models.IntegerField(blank=True, null=True)
incident_number = models.CharField(max_length=20, default=0, blank=True)
patient_name = models.CharField(max_length=20, default=0, blank=True)
medic_unit = models.ForeignKey('components.MedicUnit', related_name='medic_unit', on_delete=models.PROTECT, blank=True)
free_text = models.CharField(max_length=1000, default=0, blank =True)
drug_total = computed_property.ComputedIntegerField(compute_from='add_drug')
I would like to store the medication total for each drug in the database. I understand from researching this that storing totals isn't best practice in most cases but I believe this is one exception. Obviously the calculated integer field isn't the answer, but none of my research has gotten me any closer to the answer. My guess is that I first need to filter by 'drug_name' and then calculate the added or subtracted drugs from the previous total in a function within my model. Thanks ahead of time for any help.

I might be getting wrongly what you need. But it seems to me that you just need to compute the drug_total + drug_added - drug_removed is that correct?
Add this to the bottom of your model:
def get_current_amount_of_drug(self):
return self.drug_total - self.drug_removed + self.drug_added
Then from any view or other place where you need it - you can just call
safe = Safe.objects.filter(drug_name="My Drug").first() # or however you filter them and then just get
safe.get_current_amount_of_drug()
Maybe the documentation would help you more:
https://docs.djangoproject.com/en/2.2/topics/db/models/#model-methods

Related

What's the most efficient way to retrieve django queryset with the hightest number of posts for a related name?

I'm currently working on a website where advertisements will be posted to display vehicles for sale and rent. I would like to retrieve a queryset that highlights only one car brand (i.e. Audi) which has the highest number of posts for the respective model. Example:
Displaying the Audi brand because it has the highest number of related posts.
My question is, what's the most efficient way of doing this? I've done some work here but I'm pretty sure this is not the most efficient way. What I have is the following:
# Algorithm that is currently retrieving the name of the brand and the number of related posts it has.
def top_brand_ads():
queryset = Advertisement.objects.filter(status__iexact="Published", owner__payment_made="True").order_by('-publish', 'name')
result = {}
for ad in queryset:
# Try to update an existing key-value pair
try:
count = result[ad.brand.name.title()]
result[ad.brand.name.title()] = count + 1
except KeyError:
# If the key doesn't exist then create it
result[ad.brand.name.title()] = 1
# Getting the brand with the highest number of posts from the result dictionary
top_brand = max(result, key=lambda x: result[x]) # Returns for i.e. (Mercedes Benz)
context = {
top_brand: result[top_brand] # Retrieving the value for the top_brand from the result dict.
}
print(context) # {'Mercedes Benz': 7} -> Mercedes Benz has seven (7) related posts.
return context
Is there a way I could return a queryset instead without doing what I did here or could this be way more efficient?
If the related models are needed, please see below:
models.py
# Brand
class Brand(models.Model):
name = models.CharField(max_length=255, unique=True)
image = models.ImageField(upload_to='brand_logos/', null=True, blank=True)
slug = models.SlugField(max_length=250, unique=True)
...
# Methods
# Owner
class Owner(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
telephone = models.CharField(max_length=30, blank=True, null=True)
alternate_telephone = models.CharField(max_length=30, blank=True, null=True)
user_type = models.CharField(max_length=50, blank=True, null=True)
payment_made = models.BooleanField(default=False)
expiring = models.DateTimeField(default=timezone.now)
...
# Methods
# Advertisement (Post)
class Advertisement(models.Model):
STATUS_CHOICES = (
('Draft', 'Draft'),
('Published', 'Published'),
)
owner = models.ForeignKey(Owner, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=150, blank=True, null=True)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, blank=True, null=True)
publish = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Draft')
...
# Other fields & methods
Any help would be greatly appreciated.
Since you need brands, let's query on Brand model:
Brand.objects.filter(advertisement__status__iexact="Published").\
filter(advertisement__owner__payment_made=True).\
annotate(published_ads=Count('advertisement__id')).\
order_by('-published_ads')
However, even in your proposed solution, you can improve a little bit:
Remove the order_by method from your queryset. It doesn't affect the final result but adds some overhead, especially if your Advertisement model is not indexed on those fields.
Every time you call ad.brand you are hitting the database. This is called the N+1 problem. You are in a loop of n, you make n extra db access. You can use select_related to avoid such problems. In your case: Advertisement.objects.select_related('brand')...
Did you try the count method?
from django.db.models import Count
Car.objects.annotate(num_views=Count('car_posts_related_name')).order_by('num_views')

How do I calculate percentages from two tables in Django

Im pretty new to the Django Framework and I am stuck at calculating a percentage. Heres the problem:
I have two tables SocialCase and Donation:
class SocialCase(models.Model):
title = models.CharField(max_length=200)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
organizer = models.TextField(max_length=200, null=True, blank=False)
description = models.TextField(null=True, blank=False)
created = models.DateTimeField(auto_now_add=True)
profile_image = models.ImageField(upload_to='static/images/')
case_tags = models.ManyToManyField('events.Tag')
target_donation = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
class Donation(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
social_case = models.ForeignKey(SocialCase, on_delete=models.CASCADE)
raised = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
I want to use a #property method to calculate the percentage between raised and target_donation
The target_donation field represents the total money needed for
this social case. The target_donation will be set when the
Social Case is created.
The raised field represents the amount of money that has been
currently raised. The raised field will be a dynamic value that
will increment with each donation
I want to calculate the percentage on the SocialCase model.
How do I bring the raised column from Donations model in order to calculate the percentage of each Social Case and output it in the HTML template?
Thank you verry much, and and sorry if this a simple question, Im still a newbie and couldnt find anything in the Django Documentation.
Kind regards,
Sergiu
When you define a ForeignKey it creates a "reverse" field on the related object. You can name this using related_name, otherwise it defaults to <modelname>_set (modelname is lowercased). In this case, donation_set
That's probably what you were missing. The code will be something like
#property
def percent_raised(self):
total = 0.0
for donation in self.donation_set.all():
total += float( donation.raised)
return total / float( self.target_donation) * 100.0
It's more efficient in this case but much less generalizable, to calculate the sum of donations in the DB query using an aggregation function. See the cheat sheet here (third example using Avg, but in this case you'd want Sum not Avg)

Is it possible in Django to iterate the creation of a model?

I'm trying to create a model with 100 columns. Is it possible to iterate in my models.py file to create many entries at once?
This is an example of what I'm trying to accomplish.
class AgeFactor(models.Model):
'''
Used to populate a table with all the age-factor numbers
'''
gender = models.CharField(max_length=50, null=True, blank=True)
code = models.CharField(max_length=50, null=True, blank=True)
event_code = models.CharField(max_length=50, null=True, blank=True)
oc = models.DecimalField(max_digits=9, decimal_places=4)
for lp in range(101):
if lp > 4:
(f"{lp} = models.DecimalField(max_digits=9, decimal_places=4)")
Thank you.
Yes, although this is not how to do that. What you can do is use a loop mechanism with:
class AgeFactor(models.Model):
'''
Used to populate a table with all the age-factor numbers
'''
gender = models.CharField(max_length=50, null=True, blank=True)
code = models.CharField(max_length=50, null=True, blank=True)
event_code = models.CharField(max_length=50, null=True, blank=True)
oc = models.DecimalField(max_digits=9, decimal_places=4)
for lp in range(5, 101):
locals()[f'data{lp}'] = models.DecimalField(max_digits=9, decimal_places=4)
This will make a set of fields named data5, data6, data7, … data100.
That being said, it is rather "ugly", and often you can better define a many-to-one relation with a ForeignKey [Django-doc] since that makes the data more "flat".
Furthermore if I run this locally it takes some time for Django to fully evaluate the model, so I do not know how well Django scales with a large amount of fields (the for loop itself is not the problem, but Django has a lot of bookkeeping for these fields).
Finally querying can also be slow, because the database has to load all these columns, furthermore queries will be quite large, because the SELECT part will enumerate all the data… fields.

Django - loop using models.PositiveIntegerField variable

I have this code:
class Zapas(models.Model):
tym_domaci = models.ForeignKey(Tym, on_delete=models.CASCADE)
tym_hoste = models.ForeignKey(Tym, on_delete=models.CASCADE)
datum = models.DateTimeField('datum zapasu')
goly_domaci = models.PositiveIntegerField(default=0)
goly_hoste = models.PositiveIntegerField(default=0)
for x in range (goly_domaci):
strelec = models.ForeignKey(Hrac, on_delete=models.CASCADE, limit_choices_to={Hrac.tym == tym_domaci})
nahraval = models.ForeignKey(Hrac, on_delete=models.SET_NULL, blank=True, null=True, limit_choices_to={Hrac.tym == tym_domaci})
for x in range (goly_hoste):
strelec = models.ForeignKey(Hrac, on_delete=models.CASCADE, limit_choices_to={Hrac.tym == tym_hoste})
nahraval = models.ForeignKey(Hrac, on_delete=models.SET_NULL, blank=True, null=True, limit_choices_to={Hrac.tym == tym_hoste})
What i am trying to do, is to load all the players that scored a goal and players that assisted on it (if there is any) for each team. Problem is, that i cannot use goly_domaci and goly_hoste in the for loop because they are PositiveIntegerField and not an Integer. Is there any way to convert the PositiveIntegerField into Integer? Or can I even use the for loop like this? I am new to python and Django so I really dont know how to solve it. Thanks for help :-)
No that is not the reason. This code doesn't make sense; you can't define fields dynamically like that. Fields depend on columns in the database, a model must have a fixed number of fields. And goly_domaci is itself a field, it doesn't have a value at this point; it only has a value when accessed from an actual instance, at which point it's far too late to be defining other fields.
But this isn't what you want to do anyway. There is no point defining all those separate foreign keys to the same target model. What you want to do is define a separate model for Goals, which points to this model (I assume Zapas means Game).
class Goal(models.Model):
game = models.ForeignKey(Zapas)
team = models.ForeignKey(Tym, on_delete=models.CASCADE)
strelec = models.ForeignKey(Hrac, on_delete=models.CASCADE)
nahraval = models.ForeignKey(Hrac, on_delete=models.SET_NULL, blank=True, null=True)
Then you can drop your goly_domaci and goly_hoste fields altogether, as you can calculate them when you need to display them:
goly_hoste = my_zpas.goal_set.filter(team=my_zpas.tym_hoste).count()
goly_domaci = my_zpas.goal_set.filter(team=my_zpas.tym_domaci).count()

Django 'distinct' returning multiple values

I know this subject was discussed here, but I really can't get this to work... :(
I have this DeviceLocation model:
class DeviceLocation(models.Model):
device = models.ForeignKey(Device, related_name='locations', null=True, on_delete=models.SET_NULL)
device_imei = models.CharField(max_length=20)
user = models.ForeignKey(User, related_name='device_locations', null=True, on_delete=models.SET_NULL)
user_username = models.CharField(max_length=30, null=True)
timestamp = models.DateTimeField()
latitude = models.FloatField()
longitude = models.FloatField()
accuracy = models.FloatField()
speed = models.FloatField()
I want to get the last location for each different device, in the last 5 min. So I tried this:
time_now = datetime.now()
time_from = time_now.replace(minute=time_now.minute - 5)
last_device_locations = DeviceLocation.objects.filter(timestamp__range=(time_from, time_now)).distinct('device')
The thing is, distinct is not working... Return multiple results for the same device.
I searched the site and found a work-around using values, even so I can't do it:
DeviceLocation.objects.filter(timestamp__range=(time_from, time_now)).values('device', 'timestamp').order_by('device').distinct('device')
but this still doesn't work... :(
Any hints?
Thank you!
Can you try this:
DeviceLocation.objects.filter(timestamp__range=(time_from, time_now)).order_by('device', '-timestamp').distinct('device')
I'm not sure, but it may be that the .values() call is interacting with the select distinct.
Also, you want to add '-timestamp' to the .order_by() call, to make sure that you get the most recent location for each device.

Categories

Resources