Bar Chart - Information is rendered after a data load (admin section)

Above chart is generated dinamically based on the information gathered by get_sales_report)(), a inner method defined by Sale model
To update the charts, please authenticate as admin, update the SALES table information and refresh the page.
Note: Admin account can be created using the createsuperuser command.

The Routing Settings

This page has a simple rule defined in the app/ file:

# Contents of file: app/

urlpatterns = [
    # Charts from Input
    path('charts-load'  , views.charts_load,  name='charts-load'  ),

Render Flow

The code that render this page use the information from SALES table.
The model comes with a inner method that selects all rows: get_sales_report().
Once the SALES rows are selected, controller read the page template and inject the information.

Source: app/ - charts_load():

# Partial content from file: app/
def charts_load(request):
    context = {'segment': 'charts_from_load'}
    html_template = loader.get_template('charts-from-load.html')

    # -----------------------------------------------
    # Extract data from Sale table 
    # -----------------------------------------------

    sales, labels = Sale.get_sales_report()
    data = [
            'y': year,
            'a': '{:.2f}'.format(sales[year].get('A')),
            'b': '{:.2f}'.format(sales[year].get('B')),
            'c': '{:.2f}'.format(sales[year].get('C'))
        } for year in sales

    context['chart_data'] = json.dumps({
        'element': 'morris-bar-chart',
        'data': data,
        'xkey': 'y',
        'barSizeRatio': 0.70,
        'barGap': 3,
        'resize': True,
        'responsive': True,
        'ykeys': ['a', 'b', 'c'],  # it can be custom
        'labels': labels,
        'barColors': ['0-#1de9b6-#1dc4e9', '0-#899FD4-#A389D4', '#04a9f5']  # it can be custom

    return HttpResponse(html_template.render(context, request))

Database Model - SALES

The model comes with a inner method that selects all rows: get_sales_report().

class Sale(models.Model):
    amount = models.FloatField(_('amount'), db_index=True)
    product_name = models.CharField(_('product name'), max_length=40, db_index=True)
    created_time = models.DateTimeField(verbose_name=_('creation On'), db_index=True)
    updated_time = models.DateTimeField(verbose_name=_('modified On'), auto_now=True)

    class Meta:
        verbose_name = _('sale')
        verbose_name_plural = _('sales')

    def get_sales_report(cls):
        annotates = {'total_amount': Sum('amount')}

        sales = cls.objects.annotate(
        ).values('product_name', 'year').order_by().annotate(**annotates)

        data = {}
        for sale in sales:

            if sale['year'].year not in data:
                data[sale['year'].year] = {}

            data[sale['year'].year][sale['product_name']] = sale['total_amount']

        labels = list(sales.values_list('product_name', flat=True).distinct())
        return data, labels


The chart data is rendered using Morris JS, a popular open-source chart library.
The source file core/templates/charts-from-load.html.