Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional inline in Django admin?

I'm trying to figure out a way to display the following RelativeInline only if Person.is_member is True.

Current admin.py:

class RelativeInline(admin.TabularInline):
    model = Relative
    fk_name = 'member'

class PersonAdmin(admin.ModelAdmin):
    inlines = [RelativeInline,]
    ordering = ('first_name',)
    list_filter = ('is_member',)
    search_fields = ('first_name', 'last_name',)
    date_hierarchy = 'member_date'
    list_display = ('first_name', 'last_name', 'is_member', 'member_date', 'photo')

admin.site.register(Person, PersonAdmin)

The only hint I've been able to find is that I might be able to override get_formset, but I couldn't find a good example, so my feeble attempt didn't work.

Here's my failed attempt:

class RelativeInline(admin.TabularInline):
    model = Relative
    fk_name = 'member'

class PersonAdmin(admin.ModelAdmin):
    ordering = ('first_name',)
    list_filter = ('is_member',)
    search_fields = ('first_name', 'last_name',)
    date_hierarchy = 'member_date'
    list_display = ('first_name', 'last_name', 'is_member', 'member_date', 'photo')

    def get_formset(self, request, obj=None, **kwargs):
        if obj.is_member:
            inlines = [RelativeInline,]
        return super(PersonAdmin, self).get_formset(request, obj, **kwargs)

admin.site.register(Person, PersonAdmin)

There are no errors generated by this code, but no inline appears regardless of whether or not Person.is_member is True or False.


Update: A friend suggested I try changing:

inlines = [RelativeInline,]

to:

self.inlines = [RelativeInline,]

but to no avail. I also tried:

PersonAdmin.inlines = [RelativeInline,]

but the result was the same -- no error, no inline.

like image 837
Tony Guinta Avatar asked Jun 27 '10 01:06

Tony Guinta


3 Answers

Your original solution was pretty close. If you look in django/contrib/admin/options.py around line 290 you'll see that the inline classes are instantiated when the model admin is instantiated, after which the inlines list is ignored. So setting this list later on in get_formsets() has no effect.

However, you're correct that get_formsets() is the thing to override in order to make your inlines conditional. The inline instances are contained in self.inline_instances, so to disable them based on the object (e.g. say I want to hide a specific inline on the "add" form) you'd override it like:

class MyAdmin(models.ModelAdmin):

    inlines = [MyInline, SomeOtherInline]

    def get_formsets(self, request, obj=None):
        for inline in self.inline_instances:
            if isinstance(inline, MyInline) and obj is None:
                continue
            yield inline.get_formset(request, obj)
like image 64
Cerin Avatar answered Nov 20 '22 11:11

Cerin


I decided to change the whole paradigm and solve my problem a different way. Instead of having a single admin for all Persons with a conditional inline, I decided to:

  1. Override the queryset to filter for members-only and keep RelativeInline with the admin for this model
  2. Create a proxy model and override its queryset to filter for non-members. The admin for this model does not include RelativeInline.

In the end, I think this is a cleaner approach. Now Members can be maintained, and relatives (non-members) can be added in the inline. The NonMemberAdmin allows for editing non-members.

models.py:

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    is_member = models.BooleanField()
    is_active = models.BooleanField(default=True)

    class Meta:
        verbose_name_plural = 'Members'
        ordering = ('first_name', 'last_name')

class PersonProxy(Person):
    class Meta:
        proxy = True
        verbose_name_plural = 'Non-Members'

class Relationship(models.Model):
    name = models.CharField(max_length=50)

class Relative(models.Model):
    member = models.ForeignKey(Person, related_name='relative_member')
    relative = models.ForeignKey(Person, related_name='relative_relative')
    relationship = models.ForeignKey(Relationship)

admin.py:

class RelativeInline(admin.TabularInline):
    model = Relative
    fk_name = 'member'


class MemberAdmin(admin.ModelAdmin):
    inlines = [RelativeInline,]
    ordering = ('first_name',)
    # list_filter = ('is_member',)
    search_fields = ('first_name', 'last_name',)
    # date_hierarchy = 'member_date'
    list_display = ('first_name', 'last_name', 'member_date')

    def queryset(self, request):
        return (super(MemberAdmin, self).queryset(request)
                .filter(is_member=True, is_active=True))


class NonMemberAdmin(admin.ModelAdmin):
    ordering = ('first_name',)
    search_fields = ('first_name', 'last_name',)
    list_display = ('first_name', 'last_name')

    def queryset(self, request):
        return (super(NonMemberAdmin, self).queryset(request)
                .filter(is_member=False, is_active=True))


admin.site.register(Person, MemberAdmin)
admin.site.register(PersonProxy, NonMemberAdmin)
like image 32
Tony Guinta Avatar answered Nov 20 '22 12:11

Tony Guinta


I realize this question's a bit old and the codebase has changed a bit; there's a cleaner point to override things at now: get_inline_instances. You can do this:

class PersonAdmin(models.ModelAdmin):

inlines = [RelativeInline,]

def get_inline_instances(self, request, obj=None):
    to_return = super(MyAdmin, self).get_inline_instances(request, obj)
    #filter out the RelativeInlines if obj.is_member is false
    if not obj or not obj.is_member:
        to_return = [x for x in to_return if not isinstance(x,RelativeInline)]
    return to_return
like image 1
aggieNick02 Avatar answered Nov 20 '22 13:11

aggieNick02