Descriptive Django Admin Search

At my job we have a growing number of people solely focused on operations (they outnumber us devs!). One of them pointed out to me that she never knew when she could search for a tenant’s name in the search bar versus using the dropdown (list_filter). It didn’t take me long to realize we have a critical usability issue in our application. We can support all this search functionality in the admin, but if a person doesn’t know what it can do they can’t use it.

Problem: Staff doesn’t know what the search does.

Solution: List the fields that are being searched.

Composition to the rescue!1

class DescriptiveSearchMixin:
    """
    Add the verbose model and field names to the search help text.

    This will include the model and field name for each search field.
    If a relationship is more than one related model, it will only display
    the last model of the relationship.

    If a ModelAdmin class has a value for search_help_text, the descriptive
    search text will not be added.
    """

    def get_search_fields(self, request):
        """
        Override get_search_fields to dynamically set search_help_text
        
        This is potentially problematic if you're doing something more complicated
        with the admin already. In my case the search functionality is vanilla so this
        works.
        """
        search_fields = super().get_search_fields(request)
        if search_fields and self.search_help_text is None:
            field_strings = []
            for search_field in search_fields:
                current_model_meta = self.model._meta
                if "__" in search_field:
                    field_path = search_field.split("__")
                    # Follow the relationships down to the last path.
                    for path in field_path:
                        field = current_model_meta.get_field(path)
                        if not getattr(field, "related_model"):
                            break
                        current_model_meta = field.related_model._meta
                else:
                    field = current_model_meta.get_field(search_field)
                # Include the human-readable version of the model and field name.
                field_strings.append(
                    f"{current_model_meta.verbose_name.title()}'s {field.verbose_name.title()}"
                )
            self.search_help_text = f'Search by: {", ".join(field_strings)}'
        return search_fields

To use this, simply inherit from the mixin class:


from my_wonderful_app.models import Plan

class PlanAdmin(DescriptiveSearchMixin, admin.ModelAdmin):
    search_fields = ["name", "creator__name", "creator__email", "organization__name"]

Now when you browse to /admin/plan/ you’ll see:

Search by: Plan’s Name, User’s Name, User’s Email, Organization’s Name

At the end of the day it doesn’t matter what you build if nobody knows about it. Be better than me at informing your users what’s possible!

  1. I can’t pass up a opportunity to hop on the Django zeitgeist (See Matthias’ post and Carlton’s post on composition)