views:

38

answers:

3

I've got a Django model containing various database model fields. One of the manager's querysets retrieves various aggregations via some annotate calls. Some of those annotations are custom made and retrieve float values from the database. Those annotations are not part of the model's fields. However, when the queryset is created, those floats turn out to be integers in the model, I guess because the there's not a model field to bound them to a float or decimal data type.

Here's some code to demostrate what I mean:

The custom aggregate classes. Note the database casts the result to a float:

class SqlCTR(aggregates.Sum):
    is_ordinal = True
    sql_function = 'SUM' 
    sql_template= "CASE WHEN sum(campaignmanager_adstats.impressions) > 0 THEN sum(campaignmanager_adstats.clicks)/sum(campaignmanager_adstats.impressions)::float ELSE 0::float END"

class CTR(Sum):
    name='CTR'
    def add_to_query(self, query, alias, col, source, is_summary):
        aggregate = SqlCTR(col, source=source, is_summary=is_summary)
        query.aggregates[alias] = aggregate

And here's the queryset:

camps =  self.select_related(depth=3).\      
  annotate( impressions=Sum('ad__adstats__impressions'),\      
  clicks=Sum('ad__adstats__clicks'),\      
  ctr=CTR('ad__adstats__clicks'),\      
  exclude(**campaignExclude).\      
  filter(**campaignArgs).order_by(sortBy)

The problem is that although the query itself runs ok and returns CTR as floats, sorts it as a float and filters it just fine (if I run the generated sql in Postgres's console), the resulting Queryset translates the value to an integer, resulting in 0s... (Remember CTR is not a model field).

How can I either make sure that the annotated values load in their right datatype to the model? Can I set a non database model field of DecimalField or FloatField which will preserve the type?

Any idea will be highly appreciated!
Thanks
Harel

A: 

According to the documentation in the django code:

source is the underlying field or aggregate definition for the column reference. If the aggregate is not an ordinal or computed type, this reference is used to determine the coerced output type of the aggregate.

So, try putting the field type in source in the constructor of SqlCTR.

aggregate = SqlCTR(col, source=source, is_summary=is_summary)

should be:

aggregate = SqlCTR(col, source=models.DecimalField(), is_summary=is_summary)
OmerGertel
Omer, that didn't seem to work (though it made perfect sense and I was very hopeful). The next comment is a shell test scenario. Note that the result of the computation should have been a decimal but comes out as 'int' (0). I tried FloatField as well...
Harel
i can't seem to write code in the comments so I'll add it in a new answer below
Harel
A: 

This code denomstrates output from the django shell after trying Omer's proposal below... Can't seem to write any code in the comments...

>>> usr = User.objects.get(username='harel')  
>>> camps = Campaign.objects.campaigns(usr, {'ctr__gt':0, 'sort':'ctr','dir':'DESC'})  
>>> for c in camps:  
...     print "%s:: %d/%d=%d  (ctr type is %s)" % (c, c.clicks, c.impressions, c.ctr, str(type(c.ctr)))  
.    ..   
Dan Schedule Test:: 10/15135=0  (ctr type is <type 'int'>)  
DTR-04-06:: 35/101827=0  (ctr type is <type 'int'>)  
US-Advertising-ad4:: 1/2991=0  (ctr type is <type 'int'>)  
Harels Test New Camp:: 51/153929=0  (ctr type is <type 'int'>)  
Commercial Team:: 161/512072=0  (ctr type is <type 'int'>)  
US-Marketing-ad3:: 1/3405=0  (ctr type is <type 'int'>)  
Harel
A: 

I'll answer my own question: Turns out (obviously), that going through the django code itself is a lot of help. My SqlCTR call had: is_ordinal = True which the comments in the django code state:

is_ordinal, a boolean indicating if the output of this aggregate
           is an integer (e.g., a count)

while I needed is_computer=True

is_computed, a boolean indicating if this output of this aggregate
           is a computed float (e.g., an average), regardless of the input
           type.
Harel