Proposal: Add CSS classes to admin changelist rows

I’d like to propose adding custom HTML/CSS classes to the admin change list table row <tr> elements. That way the styling of each table row could vary based on the model data.

As a basic example, it could be useful to highlight specific table rows, for instance greying out inactive users or highlighting users with expired passwords in red.

Thoughts?

1 Like

I have two initial reactions to this:

I agree with Ken that this is outside of the design goal of the admin.

Fair enough.

I’m still of the opinion that this is a minor cosmetic change that falls within the stated goals, especially the first paragraph. It’s definitely not a sufficiently complex feature to warrant ditching the admin in favor of custom views for projects that are already using it. But I will defer to your judgement.

Re implementation: I have already implemented this feature since I’ve been asked for it a couple of times. It’s a simple addition, but since it requires augmenting the results_list template tag so that the css classes are available in the template, I thought it might make a useful addition to the standard admin interface.

First, I apologize if I have given you, or anyone, the impression that I have “made a decision” regarding this issue (or any issue for that matter). As far as what happens within Django is concerned, my voice is just one among many. While I don’t personally think that this idea would get much traction, it’s just my opinion.

<conjecture>
Just tossing out ideas here, but I wouldn’t think that that would be necessary. I can see a couple of options here. You could override the ModelAdmin.get_changelist_instance method to attach the additional data to the ChangeList object before returning it to changelist_view. Or, you may be able to do this by overriding the ModelAdmin.get_changelist_formset method to attach the additional data there. Either way, the objective would be to alter the cl object being passed to TemplateResponse at the source so that the template has direct access to it.
</conjecture>

No need to apologize, I didn’t mean to suggest you had decided (perhaps I should have said ‘expertise’ rather than ‘judgement’?). My apologies for any misunderstanding as well - my meaning is simply that you (almost certainly) have a better idea than I do on whether this would be a sensible addition to the admin.

Interesting conjecture. ModelAdmin.get_changelist_instance could work without the need to modify the template tag. To be consistent with the current admin design a template tag is probably still the right place to do the field lookups, but I see no reason why the tag couldn’t be separate to results_list if the right data can be accessed in the ChangeList object. And in that case this feature could easily work as a standalone app. I will have a closer look at it shortly.

Hi! Was just seeing this exchange for the first time.

At work, we have a similar case:

  • For a specific ModelAdmin, we want to add a conditional CSS class to each row in the result list.
  • If we override change_list_results.html, we need to do it for ALL model admins, there is no `admin/app-name/` pattern here.
  • This sends us off in a bad direction of having to create our own derivative of the result_list that we use in our admin/app-name/change_list.html simply in order to use a different template in the inclusion_tag

See: Overriding the Django admin change_list_results.html only for some models - Stack Overflow

Here are some options to reframe the discussion…

Option 1: New options for template overriding?

(UPDATED: @KenWhitesell pointed out a factual error in the original option, so this is replaced with a different take)

We have the option to replace the template for our LawnMower model by adding a template admin/machines/lawnmower/change_list_results.html.

So we can override it, especially to swap out this part:

But here’s the problem (noted by colleague)

It was trickier than expected to figure out that change_list_results.html is rendered by Django’s result_list inclusion tag, which creates its own isolated context. So you can’t just add stuff to response.context_data - it won’t make it to the template. Had to attach the CSS map to the cl (ChangeList) object itself since that’s the one thing that actually gets passed through.

This option needs someone’s input: What would be a nice, clean way to include a custom property on <tr>? Would additional configuration on ModelAdmin be desirable? Do we continue to inject things into the cl (change list) object?

Option 2: Fetching row properties per model instance?

Since there isn’t a real notion of a row, nor its properties, we would need to add that. I think we don’t want to mix properties with the queryset and the objects we’re iterating on.

I think what we’d end up with is something like:

@admin.register(models.LawnMower)
class LawnMowerAdmin(admin.ModelAdmin):
    def get_row_properties(self, obj: models.LawnMower):
        properties = super().get_row_properties(self, obj)
        if not obj.for_sale:
            # This is a very TR-oriented suggestion.
            # We might wanna design for CSS grids etc.
            # Having a dictionary is perhaps the most flexible approach.
            properties["tr_attributes"]["css"].append("disabled")
        return properties

Then we need to adjust change_list_results.html to also read these properties.

I think we could come up with more row-related properties useful for other purposes:

  • Color-coding
  • Collapsible
  • Hidden
  • Sticky?
  • Translation stuff?
  • CSS grid flows?

Option 3: CSS selector workaround :light_bulb:

As a final addition, consider that you can actually do a lot with CSS selectors that would otherwise be much harder to do in Python and Django templates:

tr:has(td.field-some_field img.active) td {
    background-color: lime;
}

So any row that contains a <img class="active"> would have a specific background color defined on <td>s. You’d need to fill this in with your own field-specific properties, but this can go a long way for adding simple styling to row elements.

I don’t think this is accurate. See Templates which may be overridden per app or model.

(I don’t trust SO to begin with, and I certainly wouldn’t rely upon an answer that is that old. The list of templates that could be overridded by app or model was extended in version 2.1, about 5 years after that answer was written.)

That is indeed not accurate, was browsing old code on GitHub! Thanks Ken :folded_hands:

Update: Changed the first Option to reflect that!

1 Like