How can I display a Django admin inline model within a fieldset?
Bertrand Bortage posted another solution here: https://groups.google.com/forum/#!topic/django-users/yUq2Nvx_4eM
A late reply to say that I just pushed a fairly clean solution to this problem in one of my projects: https://github.com/dezede/dezede/commit/ed13ccaf34494e71fd913fd785c229052f6acdc8.
The idea is to define fieldsets_and_inlines_order in your ModelAdmin(s), an iterable of 'f' and 'i' characters (for "fieldset" and "inline") that specifies the order between the first fieldsets and inlines. If len(fieldsets_and_inlines_order) < len(fieldsets) + len(inlines), the remaining follows the original behaviour (fieldsets first, then all inlines).
Example: you have 5 fieldsets and 3 inlines, defining fieldsets_and_inlines_order = ('f', 'f', 'i', 'f', 'i') will give you: fieldset fieldset inline fieldset inline fieldset fieldset inline Hope it helps, Bertrand
I had another idea which is worth considering. Create a readonly placeholder field in your fieldsets for each inline then use jQuery to move the inlines into place for each placeholder. Something like this (jQuery omitted as I haven't written it yet):
fieldsets = (
(None, {
'fields': (
('inline_images',)
('thumbnail_image',),
('inline_authors',)
('title', 'is_active', 'order',)
),
}),
)
readonly_fields = ('inline_images', 'inline_authors')
inline_images = '<span class="replaceme inline_images"></span>'
inline_images.allow_tags = True
inline_authors = '<span class="replaceme inline_authors"></span>'
inline_authors.allow_tags = True
One more thing - there is an open Django issue asking for this positioning of inlines: https://code.djangoproject.com/ticket/4848
Unfortunately this is not possible with (the standard template from) django. If you look at the template for the change_form, you can see that inlines are always rendered separately after the fieldset: https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html
The only work-around I see is to write a custom template with respect to the order you want.
I have constructed another quite generic solution...
In your admin.py add a new field to your Inline:
class YourModelInline(admin.TabularInline):
model = YourModel
after_field = "fieldname_of_field_before_inline"
Then customize render_change_form of AdminClass of the model that holds the Inline:
class EditModelAdmin(model.ModelAdmin):
inlines = [YourModelInline,]
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
sorted_inline_formsets = {}
inline_admin_formsets = context['inline_admin_formsets']
formsets_to_remove = []
for inline_formset in inline_admin_formsets:
if hasattr(inline_formset.opts, 'after_field'):
fieldname = inline_formset.opts.after_field
if fieldname in sorted_inline_formsets:
sorted_inline_formsets[fieldname].append(inline_formset)
else:
sorted_inline_formsets.update({
fieldname: [inline_formset,]
})
formsets_to_remove.append(inline_formset)
for inline_formset in formsets_to_remove:
inline_admin_formsets.remove(inline_formset)
context.update({
'sorted_inline_formsets': sorted_inline_formsets,
'inline_admin_formsets': inline_admin_formsets
})
return super(EditModelAdmin, self).render_change_form(request, context, add=add,
change=change, obj=obj, form_url=form_url)
We are moving all Inlines with extra field into own dictionary with fieldname as key... For it to be rendered correctly create file /templates/admin/includes/fieldset.html that overrides standard django fieldset.html with following content:
{% load custom_filter %}
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
{% for field in line %}
<div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<div class="readonly">{{ field.contents }}</div>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
{% if field.field.help_text %}
<div class="help">{{ field.field.help_text|safe }}</div>
{% endif %}
</div>
{% if field.field.name %}
{% with field.field.name as fieldname %}
{% if sorted_inline_formsets|get_dict_value:fieldname != False %}
{% for inline_admin_formset in sorted_inline_formsets|get_dict_value:fieldname %}
{% include inline_admin_formset.opts.template %}
{% endfor %}
{% endif %}
{% endwith %}
{% endif %}
{% endfor %}
</div>
{% endfor %}
</fieldset>
This will add sorted inlines after the corresponding field... Now you only need the custom_filter for working with the dictionary in django template, create templatetags/custom_filter.py and add:
@register.filter
def get_dict_value(dict, key):
if key in dict:
return dict[key]
else:
return False
And voila: You can enter any fieldname into any Inline to add it after that field... It is a bit work to setup but if you have several inlines to sort it might be a cleaner way...