0

I want to take data (amount_spent) from the field of each user and add those numbers up and display them in another field (total_revenue) from a different model (RevenueInfo).

from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.db import models
from django import forms, views

# Create your models here.
#LoginInfo is being used, LoginForms in forms.py is
class LoginInfo(models.Model):
    username = models.CharField('', max_length=10)
    password = models.CharField('', max_length=15)

class ExtendedProfile(models.Model):
    user = models.OneToOneField(User)
    amount_spent = models.DecimalField(max_digits=6, decimal_places=2)

class RevenueInfo(models.Model):
    total_amount_spent = models.DecimalField(max_digits=6, decimal_places=2, default=0)
    total_revenue = models.ForeignKey(ExtendedProfile, null=True)

class Product(models.Model):
     category = models.CharField(max_length=100)
     name = models.CharField(max_length=100)
     description = models.TextField()
     #photo = models.ImageField()
     price_CAD = models.DecimalField(max_digits=6, decimal_places=2)
     quantity = models.DecimalField(max_digits=2, decimal_places=0, null=True)

How could I go about this? Would I iterate of each Usermodel and find User.amount_spent then add that to RevenueInfo.total_revenue? I'm not sure how to put that into code. Also I'm pretty sure I don't need both total_amount_spent and total_revenue but I feel like I need a ForeignKey

Igonato
  • 10,175
  • 3
  • 35
  • 64
Amon
  • 2,725
  • 5
  • 30
  • 52

3 Answers3

1

Yes, you can write a method for that in your model. There are 2 ways for it. 1) Writing a method that calculates the values and sets it to a instance value. 2) Writing a method that calculates the value and directly returns it.

For example purpose, here is the code for 2nd type.

# models.py
def total_amount_spent(self):
    temp_values = [int(user.amount_spent) for user in ExtendedProfile.objects.all()]
    return sum(temp_values)

And for using that value in views , but remeber it would be an integer by default

#views.py
value = RevenueInfo.total_amount_spent()
PyManiac
  • 474
  • 4
  • 12
1

Avoid iterating over database entities in python (it can get really slow). Look into aggregation, it allows you to efficiently get sum (average, max, min, etc...) of values in a database:

>>> from django.db.models import Sum
>>> ExtendedProfile.objects.all().aggregate(Sum('amount_spent'))
{'amount_spent__sum': Decimal('1234.56')}
>>> # ... do whatever you want with the return value
Igonato
  • 10,175
  • 3
  • 35
  • 64
1

You could add a classmethod to the ExtendedProfile model to aggregate the amount_spent value for each User (which bypasses the need for a separate RevenueInfo model):

from django.db.models import Sum

class ExtendedProfile(models.Model):
....
    @classmethod
    def total_user_spend(cls):
        return cls.objects.aggregate(total=Sum('amount_spent'))

Then you can get the total spend using ExtendedProfile.total_user_spend():

>>> ExtendedProfile.total_user_spend()
{'total': Decimal('1234.00')}
birophilo
  • 912
  • 10
  • 20
  • Sorry for the late reply, thanks that works. But would would the be the best way to display that on the admin page/in the actual field? Basically I would need something like `models.DecimalField('amount_spent`). Obviously that doesn't work but you know what I mean – Amon May 16 '17 at 03:07
  • I'm thinking I could retrieve the interger from the dictionary that is returned and set the default value of one of the fields to that? – Amon May 16 '17 at 03:50
  • 1
    Would need clarification to follow you completely. If this is for reference and comparison with the individual user fields then you can display the total spend on the ExtendedProfile form by adding a description using `fieldsets` in the django admin https://docs.djangoproject.com/en/1.11/ref/contrib/admin/, e.g.: – birophilo May 16 '17 at 17:46
  • 1
    class ExtendedProfileAdmin(admin.ModelAdmin): fieldsets = [ (None, { 'fields': ('user', ) }), ("Total user spend is {}".format(ExtendedProfile.total_user_spend()['total']), { 'fields':('amount_spent', ), }), ] admin.site.register(ExtendedProfile, ExtendedProfileAdmin) – birophilo May 16 '17 at 17:46
  • Sorry, I wanted a a separate view (RevenueInfo) that would display the total amount spent of all users. That's why I had a separate model, so in that model I'd want to take the `amount_spent` from each User object, accumulate the totals and display it in a revenueinfo model – Amon May 16 '17 at 20:43
  • 1
    Once it's declared as a `classmethod` you can use `ExtendedProfile.total_user_spend()` in any view, whether in a template or on an admin page, and probably don't need to hold the value in the database unless you have a particular reason. But if you would rather do that then set the value to `total_user_spend` each time an ExtendedProfile is updated. You can do that with Signals or by overriding the save method. http://stackoverflow.com/questions/35949755/django-when-should-i-use-signals-and-when-should-i-override-save-method. Also see https://docs.djangoproject.com/en/1.11/topics/signals/ – birophilo May 16 '17 at 21:04
  • Thanks a lot man, I'm gonna give you the Best Answer for that. I've got it working in in the API it seems, I'll try it on the admin page after work. What exactly does `@classmethod` do? Is putting a function inside a class not automatically designating it a method? – Amon May 16 '17 at 21:17
  • 1
    From the Python docs: https://docs.python.org/2/library/functions.html#classmethod "A class method receives the class as implicit first argument, just like an instance method receives the instance." Use it to act on the class itself rather than any particular instance of it - in this case we want all users rather than a single one. Whereas the first argument of a class method by default is a reference to the current instance of the class, called "self" by convention, vs. "cls" here. Compare `@classmethod`, `@property`, `@staticmethod`. – birophilo May 16 '17 at 22:24