Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display list_filter with count of related objects in django admin?

How can I display the count of related objects after each filter in list_filter in django admin?

class Application(TimeStampModel):

    name = models.CharField(verbose_name='CI Name', max_length=100, unique=True)
    description = models.TextField(blank=True, help_text="Business application")

class Server(TimeStampModel):
    name = models.CharField(max_length=100, verbose_name='Server Name', unique=True)
    company = models.CharField(max_length=3, choices=constants.COMPANIES.items())
    online = models.BooleanField(default=True, blank=True, verbose_name='OnLine')
    application_members = models.ManyToManyField('Application',through='Rolemembership',
            through_fields = ('server', 'application'),
            )

 
class Rolemembership(TimeStampModel):

    server = models.ForeignKey(Server, on_delete = models.CASCADE)
    application = models.ForeignKey(Application, on_delete = models.CASCADE)
    name = models.CharField(verbose_name='Server Role', max_length=50, choices=constants.SERVER_ROLE.items())
    roleversion = models.CharField(max_length=100, verbose_name='Version', blank=True)

Admin.py

@admin.register(Server)
class ServerAdmin(admin.ModelAdmin):

    save_on_top = True
    list_per_page = 30
    list_max_show_all = 500
    inlines = [ServerInLine]

    list_filter = (
        'region',
        'rolemembership__name',
        'online',
        'company',
        'location',
        'updated_on',
    )

i.e After each filter in list filter, I want to show the count of related objects.

Now it only shows the list of filter i.e location filter list

  • Toronto
  • NY
  • Chicago

I want the filter to show the count like below:

  • Toronto(5)
  • NY(3)
  • Chicago(2)

And if the filter has 0 related objects, don't display the filter.

like image 715
Stryker Avatar asked Mar 11 '23 16:03

Stryker


1 Answers

This is possible with a custom list filter by combining two ideas.

One: the lookups method lets you control the value used in the query string and the text displayed as filter text.

Two: you can inspect the data set when you build the list of filters. The docs at https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter shows examples for a start decade list filter (always shows «80s» and «90s») and a dynamic filter (shows «80s» if there are matching records, same for «90s»).

Also as a convenience, the ModelAdmin object is passed to the lookups method, for example if you want to base the lookups on the available data

This is a filter I wrote to filter data by language:

class BaseLanguageFilter(admin.SimpleListFilter):
    title = _('language')
    parameter_name = 'lang'

    def lookups(self, request, model_admin):
        # Inspect the existing data to return e.g. ('fr', 'français (11)')
        # Note: the values and count are computed from the full data set,
        # ignoring currently applied filters.
        qs = model_admin.get_queryset(request)
        for lang, name in settings.LANGUAGES:
            count = qs.filter(language=lang).count()
            if count:
                yield (lang, f'{name} ({count})')

    def queryset(self, request, queryset):
        # Apply the filter selected, if any
        lang = self.value()
        if lang:
            return queryset.filter(language=lang)

You can start from that and adapt it for your cities by replacing the part with settings.LANGUAGES with a queryset aggregation + values_list that will return the distinct values and counts for cities.

like image 69
merwok Avatar answered Apr 06 '23 05:04

merwok