Adding an annotation to django queryset to be used by django_tables2 - django

I have a model that has (among other fields) one value for the current price of an item and one value for the usual price of an item. I'd like to include a field for the percentage saving. I've done this using the #property:
#property
def percSaving(self):
aval = self.stickerprice
bval = self.currentprice
if self.stickerprice > 0:
return "%0.2f" % ((aval-bval)/aval*100) + "%"
elif self.currentprice == 0:
return "Always Free"
else:
return "100% OFF"
This works, I can add this column to my django_table2 table with:
percSaving = tables.Column(verbose_name='% Saving')
Super easy and all good. However, I am unable to sort by this column. This is because it's not one of the columns of data from the query set. So I've been trying to annotate the query set to allow for this ordering I've based by annotation attempt on this queryset api reference page and have produced this:
annoed = products.objects.annotate(percSaving=((stickerprice)-(currentprice))/(stickerprice))
However this gives me an error of "name 'stickerprice' is not defined" which I thought might be because of not using inverted commas around the field names, but I tried that and got an error saying "unsupported operand type(s) for -: 'str' and 'str'" - basically using the inverted commas forces it to view the field names as strings.
What am I doing wrong? How can I annotate a query set to allow for ordering by a column I have defined as above!

Yes, you can. With providing some more code like Model etc. I can only make some general advise.
You should use F function to calculate in the query like:
annoed = products.objects.annotate(
percSaving=((F("stickerprice") - F("currentprice")) / F("stickerprice"))
)
Check also How to make sum query with type casting and calculation in django views? which provides also some type casting hints.

Related

How do I calculate the average difference between two dates in Django?

Using Django and Python 3.7. I'm tryhing to write a query to give me the average of the difference between two dates. I have two fields in my model, both "DateTimeField"s, and I try to calculate the average difference like so
everything_avg = Article.objects.aggregate(
avg_score=Avg(F('removed_date') - F('created_on'), output_field=models.DateTimeField())
).filter(removed_date__isnull=False)
return everything_avg
but I end up getting this error when running the above
AttributeError: 'dict' object has no attribute 'filter'
What's the right way to get my average?
As the documentation says:
aggregate() is a terminal clause for a QuerySet that, when invoked, returns a dictionary of name-value pairs. *
aggregate method returns a dictionary, thus you need to make your filtering before it. Thus if you alter your code as following you would get your result:
everything_avg = Article.objects.filter(removed_date__isnull=False)\
.aggregate(
avg_score=Avg(
F('removed_date') - F('created_on'),
output_field=models.DateTimeField()
)
)
return everything_avg

Return object when aggregating grouped fields in Django

Assuming the following example model:
# models.py
class event(models.Model):
location = models.CharField(max_length=10)
type = models.CharField(max_length=10)
date = models.DateTimeField()
attendance = models.IntegerField()
I want to get the attendance number for the latest date of each event location and type combination, using Django ORM. According to the Django Aggregation documentation, we can achieve something close to this, using values preceding the annotation.
... the original results are grouped according to the unique combinations of the fields specified in the values() clause. An annotation is then provided for each unique group; the annotation is computed over all members of the group.
So using the example model, we can write:
event.objects.values('location', 'type').annotate(latest_date=Max('date'))
which does indeed group events by location and type, but does not return the attendance field, which is the desired behavior.
Another approach I tried was to use distinct i.e.:
event.objects.distinct('location', 'type').annotate(latest_date=Max('date'))
but I get an error
NotImplementedError: annotate() + distinct(fields) is not implemented.
I found some answers which rely on database specific features of Django, but I would like to find a solution which is agnostic to the underlying relational database.
Alright, I think this one might actually work for you. It is based upon an assumption, which I think is correct.
When you create your model object, they should all be unique. It seems highly unlikely that that you would have two events on the same date, in the same location of the same type. So with that assumption, let's begin: (as a formatting note, class Names tend to start with capital letters to differentiate between classes and variables or instances.)
# First you get your desired events with your criteria.
results = Event.objects.values('location', 'type').annotate(latest_date=Max('date'))
# Make an empty 'list' to store the values you want.
results_list = []
# Then iterate through your 'results' looking up objects
# you want and populating the list.
for r in results:
result = Event.objects.get(location=r['location'], type=r['type'], date=r['latest_date'])
results_list.append(result)
# Now you have a list of objects that you can do whatever you want with.
You might have to look up the exact output of the Max(Date), but this should get you on the right path.

Getting a value from a different class in django

As new to both Python and Django I have encounterd what I think is a simple problem, but which does not want to go away. I have a "table" with factors obtained from
class TjgFaktor(models.Model):
typ = models.CharField(max_length=2)
factor = models.FloatField()
Next I have another class which is foreign-keyed to this:
class Moment(models.Model):
typ = models.ForeignKey(TjgFaktor,on_delete=models.SET_NULL,null=True)
Now, what I want to do is to get the factor from the first class as an attribut to Moment. I have tried
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
in the hope of getting the correct factor. However, when I do something like
person_moment = Moment.objects.all()
for e in person_moment:
print(e.factor())
what I get is "TjgFaktor matching query does not exist".
So how should I do this? I guess it is the function: it works if I replace type=self with pk=1.
You do not need to obtain the TjgFaktor through an explicit query. If you query for some_moment.typ, Django itself will perform an implcit query to fetch the TjgFaktor that corresponds to the Moment (through the foreign key), or None, if the foreign key is set to None.
We can thus query like:
def factor(self):
tjgfaktor = self.typ
if tjgfaktor:
return tjgfaktor.factor
In case there is no related TjgFaktor, then this function will return None as well.
In case you define a large amount of values, then this fetch might be inefficient: Django will fetch all columns from the database, and since we are only interested in a single one, this will thus result in some overhead.
We can avoid that by using the following query:
def factor(self):
if self.typ_id:
return (TjgFaktor.objects.values_list('factor', flat=True)
.get(pk=self.typ_id))
Assuming factor is function within Moment class, you can access factor if Moment object has related TjgFaktor object:
def factor(self):
return self.typ.factor if self.typ else None
So, the in the factor method, you need to enter the value for typ as a string value like this: A self does not satisfy the conditions of a string parameter that is required. You could do something like this -
def factor(self):
return TjgFaktor.objects.get(typ="YOUR_TYPE_IN_STRING").factor
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
You can't actually compare the object of Moment with objects in TjgFaktor. You can directly access the values of parent model or foreignkey directly by doing like this.
e.typ.factor #this will directly give you factor values of foreign key.
Or you can compare with
def factor(self):
return TjgFaktor.objects.get(typ=self.typ.id).factor

Django get count of each age

I have this model:
class User_Data(AbstractUser):
date_of_birth = models.DateField(null=True,blank=True)
city = models.CharField(max_length=255,default='',null=True,blank=True)
address = models.TextField(default='',null=True,blank=True)
gender = models.TextField(default='',null=True,blank=True)
And I need to run a django query to get the count of each age. Something like this:
Age || Count
10 || 100
11 || 50
and so on.....
Here is what I did with lambda:
usersAge = map(lambda x: calculate_age(x[0]), User_Data.objects.values_list('date_of_birth'))
users_age_data_source = [[x, usersAge.count(x)] for x in set(usersAge)]
users_age_data_source = sorted(users_age_data_source, key=itemgetter(0))
There's a few ways of doing this. I've had to do something very similar recently. This example works in Postgres.
Note: I've written the following code the way I have so that syntactically it works, and so that I can write between each step. But you can chain these together if you desire.
First we need to annotate the queryset to obtain the 'age' parameter. Since it's not stored as an integer, and can change daily, we can calculate it from the date of birth field by using the database's 'current_date' function:
ud = User_Data.objects.annotate(
age=RawSQL("""(DATE_PART('year', current_date) - DATE_PART('year', "app_userdata"."date_of_birth"))::integer""", []),
)
Note: you'll need to change the "app_userdata" part to match up with the table of your model. You can pick this out of the model's _meta, but this just depends if you want to make this portable or not. If you do, use a string .format() to replace it with what the model's _meta provides. If you don't care about that, just put the table name in there.
Now we pick the 'age' value out so that we get a ValuesQuerySet with just this field
ud = ud.values('age')
And then annotate THAT queryset with a count of age
ud = ud.annotate(
count=Count('age'),
)
At this point we have a ValuesQuerySet that has both 'age' and 'count' as fields. Order it so it comes out in a sensible way..
ud = ud.order_by('age')
And there you have it.
You must build up the queryset in this order otherwise you'll get some interesting results. i.e; you can't group all the annotates together, because the second one for count depends on the first, and as a kwargs dict has no notion of what order the kwargs were defined in, when the queryset does field/dependency checking, it will fail.
Hope this helps.
If you aren't using Postgres, the only thing you'll need to change is the RawSQL annotation to match whatever database engine it is that you're using. However that engine can get the year of a date, either from a field or from its built in "current date" function..providing you can get that out as an integer, it will work exactly the same way.

How do I get the related objects In an extra().values() call in Django?

Thank to this post I'm able to easily do count and group by queries in a Django view:
Django equivalent for count and group by
What I'm doing in my app is displaying a list of coin types and face values available in my database for a country, so coins from the UK might have a face value of "1 farthing" or "6 pence". The face_value is the 6, the currency_type is the "pence", stored in a related table.
I have the following code in my view that gets me 90% of the way there:
def coins_by_country(request, country_name):
country = Country.objects.get(name=country_name)
coin_values = Collectible.objects.filter(country=country.id, type=1).extra(select={'count': 'count(1)'},
order_by=['-count']).values('count', 'face_value', 'currency_type')
coin_values.query.group_by = ['currency_type_id', 'face_value']
return render_to_response('icollectit/coins_by_country.html', {'coin_values': coin_values, 'country': country } )
The currency_type_id comes across as the number stored in the foreign key field (i.e. 4). What I want to do is retrieve the actual object that it references as part of the query (the Currency model, so I can get the Currency.name field in my template).
What's the best way to do that?
You can't do it with values(). But there's no need to use that - you can just get the actual Collectible objects, and each one will have a currency_type attribute that will be the relevant linked object.
And as justinhamade suggests, using select_related() will help to cut down the number of database queries.
Putting it together, you get:
coin_values = Collectible.objects.filter(country=country.id,
type=1).extra(
select={'count': 'count(1)'},
order_by=['-count']
).select_related()
select_related() got me pretty close, but it wanted me to add every field that I've selected to the group_by clause.
So I tried appending values() after the select_related(). No go. Then I tried various permutations of each in different positions of the query. Close, but not quite.
I ended up "wimping out" and just using raw SQL, since I already knew how to write the SQL query.
def coins_by_country(request, country_name):
country = get_object_or_404(Country, name=country_name)
cursor = connection.cursor()
cursor.execute('SELECT count(*), face_value, collection_currency.name FROM collection_collectible, collection_currency WHERE collection_collectible.currency_type_id = collection_currency.id AND country_id=%s AND type=1 group by face_value, collection_currency.name', [country.id] )
coin_values = cursor.fetchall()
return render_to_response('icollectit/coins_by_country.html', {'coin_values': coin_values, 'country': country } )
If there's a way to phrase that exact query in the Django queryset language I'd be curious to know. I imagine that an SQL join with a count and grouping by two columns isn't super-rare, so I'd be surprised if there wasn't a clean way.
Have you tried select_related() http://docs.djangoproject.com/en/dev/ref/models/querysets/#id4
I use it a lot it seems to work well then you can go coin_values.currency.name.
Also I dont think you need to do country=country.id in your filter, just country=country but I am not sure what difference that makes other than less typing.