[Fixed]-Making inlines conditional in the Django admin

12๐Ÿ‘

โœ…

Thanks to the comments for a change in 1.4. My implementation here wasnโ€™t thread safe either, so it really should have been deleted.

Since get_formsets is passed the object and calls get_inline_instances, we can modify both functions to act on the object.

This should work:

class ThingAdmin(admin.ModelAdmin):
    model = Thing

    inlines = [inline]
    other_set_of_inlines = [other_inline]

    def get_inline_instances(self, request, obj=None):
        #                                    ^^^ this is new
        inline_instances = []

        if obj.date > datetime.date(2012, 1, 1):
            inlines = self.inlines
        else:
            inlines = self.other_set_of_inlines

        for inline_class in inlines:
            inline = inline_class(self.model, self.admin_site)
            if request:
                if not (inline.has_add_permission(request) or
                        inline.has_change_permission(request) or
                        inline.has_delete_permission(request)):
                    continue
                if not inline.has_add_permission(request):
                    inline.max_num = 0
            inline_instances.append(inline)
        return inline_instances

    def get_formsets(self, request, obj=None):
        for inline in self.get_inline_instances(request, obj):
            #                                           ^^^^^ this is new
            yield inline.get_formset(request, obj)

10๐Ÿ‘

As of Django 2.2.2 (current latest version as of this writing), I would use the solution provided earlier by @aggieNick02, which is to override get_inline_instances shown below.

class ThingAdmin(models.ModelAdmin):
    inlines = [MyInline,]

    def get_inline_instances(self, request, obj=None):
        if not obj or obj.date >= today: return []
        return super(ThingAdmin, self).get_inline_instances(request, obj)

Iโ€™m posting this new answer because as of April 17th, 2019 in this commit, it looks like the future recommended way to do this would be to instead override the get_inlines method. So in later versions, the solution to this could look like the code below, which allows you to specify different sets of inlines and use them based on a condition.

class ThingAdmin(admin.ModelAdmin):
    model = Thing

    inlines = [inline]
    other_set_of_inlines = [other_inline]

    def get_inlines(self, request, obj):
        if obj.date > datetime.date(2012, 1, 1):
            return self.inlines
        else:
            return self.other_set_of_inlines
๐Ÿ‘คChris Hubbard

7๐Ÿ‘

You can use (django 3.0+) get_inlines method. All you have to do is override the method and define your logic,

class ThingInline(admin.StackedInline):
    """ inline needs to be returned """
    models = ThingModel


class ThingAdmin(admin.ModelAdmin):
    model = Thing
    inlines = []

    def get_inlines(self, request, obj):
        if obj.date < today:    # the date
            return [ThingInline]
        # or else
        return []

Update:
Going through this approach I faced this issue so instead of using the above mentioned approach samething can be done by overidding change_view() method,

class ThingAdmin(admin.ModelAdmin):
    model = Thing
    inlines = []

    def change_view(self, request, object_id, form_url='', extra_context=None):
        self.inlines = []
    
        try:
            obj = self.model.objects.get(pk=object_id)
        except self.model.DoesNotExist:
            pass # ... the error msg
        else:
            if obj.date < today:
                self.inlines = [ThingInline,]

        return super(ThingAdmin, self).change_view(request, object_id, form_url, extra_context)

4๐Ÿ‘

I had a complex case where the solutions I tried failed in unexpected ways (problems with readonly fields in inlines). This is the most clear and failsafe way Iโ€™ve found:

class MyAdmin(admin.ModelAdmin):

    def add_view(self, request, form_url='', extra_context=None):
        self.inlines = [InlineA, InlineC]
        return super(MyAdmin, self).add_view(request, form_url, extra_context)

    def change_view(self, request, object_id, form_url='', extra_context=None):
        self.inlines = [InlineB, InlineC, InlineD]
        return super(MyAdmin, self).change_view(request, object_id, form_url, extra_context)

This is working in Django 1.4.x.

๐Ÿ‘คlucianolev

2๐Ÿ‘

In recent version of Django, youโ€™ll need to override ModelAdmin.get_formsets. e.g.

class MyAdmin(admin.ModelAdmin):

    def get_formsets(self, request, obj=None):
        if obj:
            for _ in super(MyAdmin, self).get_formsets(request, obj):
                yield _
        else:
            for inline in self.get_specific_inlines(request):
                yield inline.get_formset(request, obj)
๐Ÿ‘คCerin

1๐Ÿ‘

The best solution for this issue is already answered here. Instead of overriding get_inline_instances override change_view method.

def change_view(self, request, object_id, form_url='', extra_context=None):
    self.inlines = []
    try:
        obj = self.model.objects.get(pk=object_id)
    except self.model.DoesNotExist:
        pass
    else:
        if condition:
            self.inlines = [InlineClass]
    return super(AdminClass, self).change_view(request, object_id, form_url, extra_context)
๐Ÿ‘คMayank Diwedi

0๐Ÿ‘

I had a situation where I needed to show an Inline based on the admin site that you were on for a given story.

I was able to get dynamic inlines working for Django 1.3 using the following code:

In highlights/admin.py

class HighlightInline(generic.GenericTabularInline):
    model = Highlight
    extra = 1
    max_num = 4
    fields = ('order', 'highlight')
    template = 'admin/highlights/inline.html'

class HighlightAdmin(admin.ModelAdmin):
    def regulate_highlight_inlines(self):
        highlights_enabled = Setting.objects.get_or_default('highlights_enabled', default='')
        highlight_inline_instance = HighlightInline(self.model, self.admin_site)
        highlight_found = any(isinstance(x, HighlightInline) for x in self.inline_instances)
        if highlights_enabled.strip().lower() == 'true':
            if not highlight_found:
                self.inline_instances.insert(0, highlight_inline_instance)
        else:
            if highlight_found:
                self.inline_instances.pop(0)
        print self.inline_instances

    def change_view(self, request, object_id, form_url='', extra_context=None):
        self.regulate_highlight_inlines()
        return super(HighlightAdmin, self).change_view(request, object_id)

    def add_view(self, request, form_url='', extra_context=None):
        self.regulate_highlight_inlines()   
        return super(HighlightAdmin, self).add_view(request, form_url, extra_context)

In story/admin.py

class StoryAdmin(HighlightAdmin):

One thing to note is that Iโ€™m not merely manipulating inline classes(HighlightInline) but rather, Iโ€™m changing inline instances(HighlightInline(self.model, self.admin_site)). This is because django has already constructed a list of inline instances based on a list of inline classes during the initial construction of the admin class.

๐Ÿ‘คRob Combs

0๐Ÿ‘

I think the easiest way to hack this is to call your custom funciton in get_fields, or get_fieldsets and so on, just set self.inlines in a custom function.

class XXXAdmin(admin.ModelAdmin):
    def set_inlines(self, request, obj):
        """ hack inlines models according current request.user or obj """
        self.inlines = []
        if request.user.is_superuser or request.user is obj.recorder:
            self.inlines = [AbcInline, ]

    def get_fields(self, request, obj=None):
        self.set_inlines(request, obj)  # NOTICE this line
        super(XXXAdmin, self).get_fields(request, obj)
๐Ÿ‘คWeizhongTu

0๐Ÿ‘

The most turnkey way to do this now is to override and super call to get_inline_instances.

class ThingAdmin(models.ModelAdmin):
    inlines = [MyInline,]

    def get_inline_instances(self, request, obj=None):
        unfiltered = super(ThingAdmin, self).get_inline_instances(request, obj)
        #filter out the Inlines you don't want
        keep_myinline = obj and obj.date < today
        return [x for x in unfiltered if not isinstance(x,MyInline) or keep_myinline]

This puts MyInline in when you want it and not when you donโ€™t. If you know the only inline you have in your class is MyInline, you can make it even simpler:

class ThingAdmin(models.ModelAdmin):
    inlines = [MyInline,]

    def get_inline_instances(self, request, obj=None):
        if not obj or obj.date >= today:
            return []
        return super(ThingAdmin, self).get_inline_instances(request, obj)
๐Ÿ‘คaggieNick02

Leave a comment