Customising an inline admin formset

I have a Museum which has a ForeignKey to a Town, which in turn has a ForeignKey to a Country. In fact Museum also has a ForeignKey to Country, which allows me to get hold of all of the Museums as inlines in the Country admin view, which is where I need to be able to edit them.

# models.py
class Country(models.Model):
    ...

class Town(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

class Museum(models.Model):
    town = models.ForeignKey(Town, on_delete=models.CASCADE)

# admin.py

class MuseumAdminInline(admin.TabularInline):
    model = Museum

class CountryAdmin(admin.ModelAdmin):
    model = Country
    inlines = [MuseumAdminInline]

So far so straightforward. But, I don’t just want a long list of Museums in the Country admin, pumped out by the {% for inline_admin_form in inline_admin_formset %} in admin/edit_inline/tabular.html.

Instead I want to do the equivalent of {% regroup museums by city as city_list %} with the inline_admin_formset, regrouping the inline_admin_form of Musems by City.

I’ll need to point MuseumAdminInline.template at a customised version of tabular.html.

But I am a bit unclear what else I need to do and how. It seems to me that I need to be able to customise the view and the formset factory that creates the formset. Is that correct? I am not sure which components of the chain I should be targeting.

(Eventually I will also want to make some fields of City available for each group, but I think that once I have a handle on the basic proposition I’ll understand better how to achieve that.)

Thanks for any help.

You might want to look at some packages like django-nested-inline · PyPI or django-nested-admin · PyPI

Even if they don’t quite do exactly what you want to do, reading the source code for them may give you some ideas or a direction for further research.

Actually I started with those, but I think it’s going to be cleaner and more sustainable to hardcode the customisation. I feel I am missing the big picture of what’s required, to be able to make better sense of what those packages are doing.

Oops, I missed the part in your text description where you identified that Museum has foreign keys to both Country and Town. (Admittedly, I was paying more attention to the code snippet of the model.)

<conjecture>
This being the case, I believe you could sort the instances of Museum by Town, by using the ordering attribute of the AdminInline class.
e.g.

class MuseumAdminInline(admin.TabularInline):
    model = Museum
    ordering = ['town__name']

(assuming that Town has a name attribute you wish to use)
</conjecture>
(Warning, I’m winging this - try this at your own risk.)

Indeed so, already ordered that way at the model level - but I want some much more forceful regrouping into a hierarchy of items.

I’m not sure I’m understanding what you mean by this.

What I think you’re saying is that you want something that (very loosely) is going to resemble this:

Country:
    country_field_1
    country_field_2
    country_field_3
Museums:
    Town 1:
        Museum 1:
            museum_field_1
            museum_field_2
            museum_field_3
        Museum 2:
            museum_field_1
            museum_field_2
            museum_field_3
    Town 2:
        Museum 3:
            museum_field_1
            museum_field_2
            museum_field_3
        Museum 4:
            museum_field_1
            museum_field_2
            museum_field_3

And so what you have are a visual separation of the Museum entries, even though they all reside in the one formset.

Am I close? (If not, then I’m not following you.)

<conjecture>
If I’m correct here, then I think this can be accomplished with a custom tabular.html template as you identified at top.
My first attempt would be to use the {% ifchanged tag in the
{% for inline_admin_form in inline_admin_formset %} loop to create the div under which the related Museum forms would be indented.

So I might end up with something like:

{% for inline_admin_form in inline_admin_formset %}
{% ifchanged inline_admin_form.instance.town.name %}
<h3>inline_admin_form.instance.town.name</h3>
<div>
{% endifchanged %}
 ... (all the stuff currently in the existing template)
{% ifchanged inline_admin_form.instance.town.name %}
</div>
{% endifchanged %}

</conjecture>

(Note, if you have blank forms associated with this, then there is probably more work needing to be done to handle those appropriately.)

Thanks - yes, that is essentially what I need to do.

I think I am getting somewhere with this. {% ifchanged %} is a cunning idea. Meanwhile I have also worked out how to get hold of the formset as it comes out of the factory, and if I can add additional attributes to it, that will give me another level of control.