Skip to content

Instantly share code, notes, and snippets.

@hakib
Last active January 22, 2024 15:18
Show Gist options
  • Select an option

  • Save hakib/ec462baef03a6146654e4c095142b5eb to your computer and use it in GitHub Desktop.

Select an option

Save hakib/ec462baef03a6146654e4c095142b5eb to your computer and use it in GitHub Desktop.

Revisions

  1. hakib revised this gist Sep 14, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion admin.py
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    # https://hakibenita.com/how-to-turn-django-admin-into-a-lightweight-dashboard

    from django.contrib import admin
    from django.db.models import Count, Sum, Min, Max, DateTimeField)
    from django.db.models import Count, Sum, Min, Max, DateTimeField
    from django.db.models.functions import Trunc

    from . import models
  2. hakib created this gist Dec 23, 2018.
    106 changes: 106 additions & 0 deletions admin.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,106 @@
    # https://hakibenita.com/how-to-turn-django-admin-into-a-lightweight-dashboard

    from django.contrib import admin
    from django.db.models import Count, Sum, Min, Max, DateTimeField)
    from django.db.models.functions import Trunc

    from . import models


    def get_next_in_date_hierarchy(request, date_hierarchy):
    if date_hierarchy + '__day' in request.GET:
    return 'hour'
    if date_hierarchy + '__month' in request.GET:
    return 'day'
    if date_hierarchy + '__year' in request.GET:
    return 'week'
    return 'month'


    @admin.register(models.SaleSummary)
    class LoadContractSummaryAdmin(admin.ModelAdmin):
    change_list_template = 'admin/dashboard/sales_change_list.html'
    actions = None
    date_hierarchy = 'created'
    # Prevent additional queries for pagination.
    show_full_result_count = False

    list_filter = (
    'device',
    )

    def has_add_permission(self, request):
    return False

    def has_delete_permission(self, request, obj=None):
    return False

    def has_change_permission(self, request, obj=None):
    return True

    def has_module_permission(self, request):
    return True

    def changelist_view(self, request, extra_context=None):
    response = super().changelist_view(request, extra_context=extra_context)

    # self.get_queryset would return the base queryset. ChangeList
    # apply the filters from the request so this is the only way to
    # get the filtered queryset.

    try:
    qs = response.context_data['cl'].queryset
    except (AttributeError, KeyError):
    # See issue #172.
    # When an invalid filter is used django will redirect. In this
    # case the response is an http redirect response and so it has
    # no context_data.
    return response


    # List view

    metrics = {
    'total': Count('id'),
    'total_sales': Sum('price'),
    }

    response.context_data['summary'] = list(
    qs
    .values('sale__category__name')
    .annotate(**metrics)
    .order_by('-total_sales')
    )


    # List view summary

    response.context_data['summary_total'] = dict(qs.aggregate(**metrics))


    # Chart

    period = get_next_in_date_hierarchy(request, self.date_hierarchy)
    response.context_data['period'] = period
    summary_over_time = qs.annotate(
    period=Trunc('created', 'day', output_field=DateTimeField()),
    ).values('period')
    .annotate(total=Sum('price'))
    .order_by('period')

    summary_range = summary_over_time.aggregate(
    low=Min('total'),
    high=Max('total'),
    )
    high = summary_range.get('high', 0)
    low = summary_range.get('low', 0)

    response.context_data['summary_over_time'] = [{
    'period': x['period'],
    'total': x['total'] or 0,
    'pct': \
    ((x['total'] or 0) - low) / (high - low) * 100
    if high > low else 0,
    } for x in summary_over_time]

    return response
    9 changes: 9 additions & 0 deletions models.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    # https://hakibenita.com/how-to-turn-django-admin-into-a-lightweight-dashboard

    from app.models import Sale

    class SaleSummary(Sale):
    class Meta:
    proxy = True
    verbose_name = 'Sale Summary'
    verbose_name_plural = 'Sales Summary'
    118 changes: 118 additions & 0 deletions sales_change_list.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    {% extends "admin/change_list.html" %}

    {% load i18n %}
    {% load humanize %}
    {% load mathtags %}
    {% load tz %}

    {% block content_title %}
    <h1> {% trans 'Sales Summary' %} </h1>
    {% endblock %}


    {% block result_list %}

    <div class="results">
    <table>
    <thead>
    <tr>
    <th> <div class="text"> <a href="#">Category </a> </div> </th>
    <th> <div class="text"> <a href="#">Total </a> </div> </th>
    <th> <div class="text"> <a href="#">Total Sales </a> </div> </th>
    <th> <div class="text"> <a href="#"><strong>% Of Total Sales</strong></a> </div> </th>
    </tr>
    </thead>
    <tbody>
    {% for row in summary %}
    <tr class="{% cycle 'row1' 'row2' %}">
    <td> {{ row.category }} </td>
    <td> {{ row.total }} </td>
    <td> {{ row.total_sales | default:0 }} </td>
    <td><strong> {{ row.total_sales | default:0 | percentof:summary_total.total_sales }} </strong> </td>
    </tr>
    {% endfor %}
    <tr style="font-weight:bold; border-top:2px solid #DDDDDD;">
    <td> Total </td>
    <td> {{ summary_total.total | intcomma }} </td>
    <td> {{ summary_total.total_sales | default:0 }} </td>
    <td> 100% </td>
    </tr>
    </tbody>
    </table>
    </div>

    <h2> {% blocktrans %} Sales time (by {{ period}}) {% endblocktrans %} </h2>

    <style>
    .bar-chart {
    height: 160px;
    padding-top: 60px;
    display: flex;
    justify-content: space-around;
    overflow: hidden;

    }
    .bar-chart .bar {
    background-color: #79aec8;
    flex: 100%;
    align-self: flex-end;
    margin-right: 2px;
    position: relative;
    }
    .bar-chart .bar:last-child {
    margin: 0;
    }
    .bar-chart .bar:hover {
    background-color: #417690;
    }

    .bar-chart .bar .bar-tooltip {
    user-select: none;
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    position: relative;
    z-index: 999;
    }
    .bar-chart .bar .bar-tooltip {
    position: absolute;
    top: -60px;
    left: 50%;
    transform: translateX(-50%);
    text-align: center;
    font-weight: bold;
    opacity: 0;
    }
    .bar-chart .bar:first-child .bar-tooltip {
    transform: initial;
    text-align: initial;
    left: 0;
    }
    .bar-chart .bar:last-child .bar-tooltip {
    transform: initial;
    text-align: right;
    right: 0;
    left: initial;
    }
    .bar-chart .bar:hover .bar-tooltip {
    opacity: 1;
    }
    </style>

    {% timezone 'UTC' %}
    <div class="results">
    <div class="bar-chart">
    {% for x in summary_over_time %}
    <div class="bar" style="height:{{x.pct}}%">
    <div class="bar-tooltip">
    {{x.total }}<br>
    {{x.period | date:"d/m/Y H:i"}}
    </div>
    </div>
    {% endfor %}
    </div>
    </div>
    {% endtimezone %}
    {% endblock %}

    {% block pagination %}{% endblock %}