17

I have a model that I want staff to be able to edit up to the date for the event. Like this:

class ThingAdmin(admin.ModelAdmin):
    model = Thing

    if obj.date < today: #Something like that
        inlines = [MyInline,]

The problem is, I don't have access to the obj instance at this level. I've tried overriding get_formset(), but didn't get anywhere.

Please advise?

manji
  • 47,442
  • 5
  • 96
  • 103
Jason Goldstein
  • 1,117
  • 2
  • 11
  • 20

9 Answers9

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)
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • This does not work as of Django 1.4. `__init__` no longer seems to create self.inline_instances, but now there's a get_inline_instances() method you can explicitly override. – Cerin Feb 12 '13 at 22:08
  • 2
    Seems like Cerin's comment is now out of date as this answer has been modified to use get_inline_instances – Andy Baker Jun 23 '14 at 13:17
  • 1
    This worked for me until I wanted to show/hide inlines based on whether data was filled in. I wanted to say `if obj.fieldX and obj.fieldX == "some text": [show inline "I"]`. But this was giving me management form errors, I assume because the inlines were being added to the form before POST was finished rather than on the GET as I expected. My workaround is to get the saved object and base the show/hiding of inlines on that object instead of the "obj" parameter. ie `if obj: saved_obj = ModelName.objects.get(pk = obj.pk)` then checking saved_obj's fields instead of obj's fields. (django 1.8) – jenniwren Sep 02 '18 at 17:32
  • Also, in django 1.8 I don't need to override "get_formsets". – jenniwren Sep 02 '18 at 17:37
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
  • 554
  • 5
  • 11
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)
Avishka Dambawinna
  • 1,180
  • 1
  • 13
  • 29
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
  • 519
  • 5
  • 5
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
  • 60,957
  • 96
  • 316
  • 522
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
  • 191
  • 1
  • 2
  • 7
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.

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
  • 6,124
  • 4
  • 37
  • 51
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
  • 2,557
  • 2
  • 23
  • 36