Reputation: 63
I want to show stats for account by account in for loop of template. In list of dicts or querysets.
I have 3 models: (1)AdvertisingAccount, (2)Campaign, UserProfile. I need to sum() clicks and views of Campaigns that belongs to certain AdvertisingAccounts by Foreign Key. (AdvertisingAccount also belongs to UserProfile )
Im trying to retrieve, calculate and add new key-value to dicts of queryset, but seems like I cant do it in dict method…
views.py
def ad_accounts(request):
user = request.user
accounts = user.userprofile.advertising_accounts.all()
accounts = accounts.values()
for d in accounts:
d.update(('clicks', d.campaigns.all().aggregate(Sum('clicks')) ) for k,v in d.items())
d.update(('views', d.campaigns.all().aggregate(Sum('views')) ) for k,v in d.items())
models.py
class AdvertisingAccount(models.Model):
balance = models.DecimalField(max_digits=12, decimal_places=2, default=0, blank=True, null=True)
foreign_system_name = models.CharField(max_length=30, blank=True, default=None)
foreign_system_id = models.IntegerField(default=None, blank=True, null=True)
def __str__(self):
return self.foreign_system_name + '--' + str(self.foreign_system_id)
class Campaign(models.Model):
userprofile = models.ForeignKey('users.UserProfile', on_delete=models.PROTECT, related_name='campaigns')
trade = models.ForeignKey('payments.Trade', on_delete=models.PROTECT, related_name='campaigns')
ad_account = models.ForeignKey('AdvertisingAccount', on_delete=models.PROTECT, related_name='campaigns')
views = models.IntegerField(default=0, blank=True, null=True)
clicks = models.IntegerField(default=0, blank=True, null=True)
ad_spent = models.DecimalField(max_digits=12, decimal_places=2, default=0, blank=True, null=True)
def __str__(self):
return str(self.userprofile) + '--' + str(self.ad_account.foreign_system_name) + '--' + str(self.trade.ad_budget)
template
{% for i in accounts %}
{{i.clicks}}
{{i.views}}
{% endfor %}
I got error :'dict' object has no attribute 'campaigns'
As I cant retrieve filtered data from templates, Im trying to construct custom queryset or list of dicts. Does the whole way correct? How to solve my problem?
Upvotes: 0
Views: 3480
Reputation: 476557
Please do not annotate manually, you can simply use Django's .annotate(..)
method [Django-doc]. You even used a generator here where it will result in querying a lot of times for a single Account
model.
from django.db.models import Sum
accounts = user.userprofile.advertising_accounts.annotate(
clicks=Sum('campaigns__clicks'),
views=Sum('campaigns__views')
)
The above will generate a QuerySet
of AdvertisingAccount
s, and with extra attributes .clicks
and .views
. The error is in fact entirely because you did not use AdvertisingAccount
s in the first place, but dictionaries (that originate from the .values()
, and you thus lose the context of the relations).
The annotation will run in a single query, where Django will make a query that looks like:
SELECT advertising_acount.*,
SUM(campaign.clicks) AS clicks,
SUM(campaign.views) AS views
FROM advertising_acount
LEFT OUTER JOIN campaign ON campaign.ad_account_id = advertising_acount.id
GROUP BY advertising_acount.id
This is more efficient than aggregating per AdvertisingAccount
, since then you would hit the database 2n+1 times here with n the number of AdvertisingAcount
s.
Usually it is therefore nearly always better to use model instance over a dictionary: a model class allows you to define methods, properties, it will add relations, etc. It also holds the type of the object (for example an AdvertisingAccount
) whereas two dictionaries with the same keys do not per se originate from the same models. A dictionary is just a flat stucture that maps field names to the corresponding database value. You thus "lose" a lot of context.
Upvotes: 6