Examples

This page provides a set of practical examples to demonstrate how to build various widgets. Let's assume we have a pizza delivery app with the following models:

# your_app/models.py

from django.db import models

class Pizza(models.Model):
    name = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.name

class Restaurant(models.Model):
    name = models.CharField(max_length=100, unique=True)
    menu = models.ManyToManyField(Pizza, related_name='restaurants')

    def __str__(self):
        return self.name

class Order(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, related_name='orders')
    pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE, related_name='orders')

We will define all our widgets in a dashboards.py file.

# your_app/dashboards.py

import datetime
from collections import defaultdict
from django.db.models import Count
from django.utils import timezone
from django.utils.timesince import timesince
from controlcenter import Dashboard, widgets, app_settings
from .models import Order, Pizza, Restaurant

1. Scrollable ItemList with Fixed Height

To create a scrollable list, set the height attribute on your widget. This example shows a list of pizzas ordered today from a specific restaurant.

class MenuWidget(widgets.ItemList):
    title = 'Pizzas Ordered Today'
    model = Pizza
    list_display = ['name', 'order_count']

    # Sets the widget's max-height to 300px, making it scrollable
    height = 300
    # We want all items, not just the default 10
    limit_to = None

    def get_queryset(self):
        # We could filter by a specific restaurant, e.g., 'Ciao'
        # restaurant = Restaurant.objects.get(name='Ciao')
        today = timezone.now().date()
        return Pizza.objects.filter(orders__created__gte=today)
                            .annotate(order_count=Count('orders'))
                            .order_by('-order_count')

    # Use the annotation 'order_count' from the queryset
    def order_count(self, obj):
        return obj.order_count
    order_count.short_description = 'Orders Today'

2. Sortable and Numbered ItemList

To make a list sortable, set sortable = True. To add a row number column, include app_settings.SHARP in list_display.

class LatestOrdersWidget(widgets.ItemList):
    title = 'Latest 20 Orders'
    model = Order
    queryset = Order.objects.select_related('pizza').order_by('-created')
    limit_to = 20
    sortable = True

    # Add '#' for row numbers, 'pizza' for the relationship, and a custom method 'ago'
    list_display = (app_settings.SHARP, 'pk', 'pizza', 'ago')

    # A custom method to display how long ago the order was created
    def ago(self, obj):
        return timesince(obj.created)
    ago.short_description = 'When'

This SingleBarChart shows the total number of orders per restaurant. We use the legend() method to display the order count, as Chartist.js doesn't show values on bars by default.

class RestaurantPopularityChart(widgets.SingleBarChart):
    title = 'Most Popular Restaurants'
    model = Restaurant

    class Chartist:
        options = {
            'axisY': {'onlyInteger': True}
        }

    def legend(self):
        # Display series values in the legend
        return [y for x, y in self.values]

    def values(self):
        # Returns pairs of (restaurant name, order count)
        queryset = self.get_queryset()
        return queryset.annotate(order_count=Count('orders')) \
                       .order_by('-order_count') \
                       .values_list('name', 'order_count')

4. Line Chart with Multiple Series

This example shows daily order counts for multiple restaurants over the last week. It demonstrates handling multiple data series and dealing with missing data points.

RESTAURANTS = ['Mama', 'Ciao', 'Sicilia']

class WeeklyOrdersChart(widgets.LineChart):
    title = 'Orders This Week'
    width = widgets.LARGER # Make it wider

    def legend(self):
        return RESTAURANTS

    def labels(self):
        # X-axis: The last 7 days
        today = timezone.now().date()
        return [(today - datetime.timedelta(days=i)).strftime('%d.%m') 
                for i in range(7)]

    def series(self):
        # Fetch the prepared data
        data = self.values
        # Prepare a list of series for Chartist.js
        # Each inner list corresponds to a restaurant in the legend
        return [
            [data.get(restaurant, {}).get(label, 0) for label in self.labels()]
            for restaurant in self.legend()
        ]

    def values(self):
        # Fetch order counts grouped by restaurant and day
        today = timezone.now().date()
        week_ago = today - datetime.timedelta(days=7)

        queryset = Order.objects.filter(created__gte=week_ago) \
                                .extra({'day': "date(created)"}) \
                                .values('restaurant__name', 'day') \
                                .annotate(count=Count('id'))

        # Restructure the data into a nested dictionary:
        # {'Ciao': {'23.01': 5, '24.01': 8}, 'Mama': ...}
        data = defaultdict(dict)
        for row in queryset:
            day_str = datetime.datetime.strptime(row['day'], '%Y-%m-%d').strftime('%d.%m')
            data[row['restaurant__name']][day_str] = row['count']
        return data