22

I have a model Data, associated to a table like this (The model Data is made up of only IntegerField):

subject | year | quarter | sales |
----------------------------------
   1    | 2010 |   1     | 20    |
   1    | 2010 |   2     | 100   |
   1    | 2010 |   3     | 100   |
   1    | 2010 |   4     | 20    |
   1    | 2011 |   1     | 30    |
   1    | 2011 |   2     | 50    |
   1    | 2011 |   4     | 40    |
   2    | 2010 |   1     | 30    |
   2    | 2010 |   2     | 20    |
 [..-GO ON this way...]

I want to have a django-admin table, in read-only having columns (current year = 2011, quarter = 1)

subject | sales current year | sales current quarter | sales last year | sales current quarter last year |
----------------------------------------------------------------------------------------------------------
  1     |  110               |  30                   |  240            |  20
[AND SO ON]

The question is: It is possible do that using django-admin? What's the way out?

dan-klasson
  • 13,734
  • 14
  • 63
  • 101
zambotn
  • 735
  • 1
  • 7
  • 20

2 Answers2

41

You can use methods on your Model or your ModelAdmin as items for list_display. See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display

Since these are methods that might be useful outside the admin, as well, I'd suggest adding them to your Model.

from django.db.models import Sum

class Data(models.Model):
    ...

    # Method used by `get_current_year_sales` and `get_last_year_sales`
    # to stay DRY. Not for use directly in admin.
    def get_year_sales(self, year):
        qs = self.model._default_manager.filter(year=year)
        sales_agg = qs.aggregate(Sum('sales'))
        return sales_agg['sales__sum']

    # Method used by `get_current_quarter_sales` and `get_last_quarter_sales`
    # to stay DRY. Not for use directly in admin.
    def get_quarter_sales(self, year, quarter):
        qs = self.model._default_manager.filter(year=year, quarter=quarter)
        sales_agg = qs.aggregate(Sum('sales'))
        return sales_agg['sales__sum']

    def get_current_year_sales(self):
        return self.get_year_sales(datetime.now().year)
    get_current_year_sales.short_description = 'Sales (Current Year)'

    def get_last_year_sales(self):
        return self.get_year_sales(datetime.now().year-1)
    get_last_year_sales.short_description = 'Sales (Last Year)'

    def get_current_quarter_sales(self):
        # Determine current quarter logic here as `current_quarter`
        # `quarter_year` will likely be same as current year here,
        # but will need to be calculated for previous quarter
        return self.get_quarter_sales(quarter_year, current_quarter)
    get_current_quarter_sales.short_description = 'Sales (Current Quarter)'

    def get_current_quarter_sales(self):
        # Logic here to determine last quarter as `last_quarter`
        # Logic to determine what year last quarter was in as `quarter_year`
        return self.get_quarter_sales(quarter_year, last_quarter)
    get_last_quarter_sales.short_description = 'Sales (Last Quarter)'

The short_description attribute determines what the admin will show as the row header for these methods. So, once you have all this in place, you need only modify your ModelAdmin's list_display attribute like:

class DataAdmin(admin.ModelAdmin):
    ...
    list_display = ('subject', 'get_current_year_sales', 'get_last_year_sales', 'get_current_quarter_sales', 'get_last_quarter_sales')
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Last question, if i want to sort the column it doesn't work because these are methods and not attributes, true? Thank you! – zambotn Feb 06 '12 at 20:37
  • 1
    Check out [this](http://stackoverflow.com/questions/2168475/django-admin-how-to-sort-by-one-of-the-custom-list-display-fields-that-has-no-d) and [this](http://stackoverflow.com/questions/2647632/how-to-allow-sorting-in-the-django-admin-by-a-custom-list-display-field-which-d) – dan-klasson Feb 06 '12 at 20:47
  • 1
    True. You can add the ability to sort by telling django what field to use with `.admin_order_field = 'my_field'`, but there's not really a good field to tie it to in this scenario. – Chris Pratt Feb 06 '12 at 20:49
  • If the new column doesn't show up, try restarting the Django server. – Ryan Allen Oct 23 '13 at 21:58
  • How can I set label for this custom field 'get_current_year_sales', for example 'Current Year Sale' w/o 'get'? – Yaser Khahani Oct 01 '18 at 10:58
  • In addition to model's attributes/methods, `list_display` might contain callables, or attributes of `ModelAdmin`. – x-yuri Nov 06 '18 at 20:27
  • Register the DataAdmin as the second parameter to admin.site.register: `admin.site.register(Data, DataAdmin)` – Peheje May 17 '19 at 11:17
10

Something like this should work (untested):

# models.py
class Data(models.Model):
    year = models.DateField()
    sales = models.IntegerField()
    # ...

    def sales_current_year(self):
        return self.model._default_manager.get_queryset().filter(year=2012).annotate(Sum('sales'))

 # admin.py
 class DataAdmin(admin.ModelAdmin):
      list_display = ('sales_current_year',)
merwok
  • 6,779
  • 1
  • 28
  • 42
dan-klasson
  • 13,734
  • 14
  • 63
  • 101