Conditional inline in Django admin?
Asked Answered
S

3

7

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.

Schiffman answered 27/6, 2010 at 1:48 Comment(0)
S
1

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)
Schiffman answered 27/6, 2010 at 19:42 Comment(0)
D
4

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)
Destalinization answered 13/2, 2012 at 17:38 Comment(1)
update for 2014 and django 1.6: for inline in self.get_inline_instances(request, obj):Shahjahanpur
S
1

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)
Schiffman answered 27/6, 2010 at 19:42 Comment(0)
U
1

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
Unexpressed answered 17/10, 2017 at 15:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.