0

In the Django admin, I have an Inline, and I would like to filter the list of rows by the parent object.

I can override get_queryset(request) in my Inline, but I don't have access to the parent object.

This snippet is from Django's options.py:

    def get_formset_kwargs(self, request, obj, inline, prefix):
        formset_params = {
            "instance": obj,
            "prefix": prefix,
            "queryset": inline.get_queryset(request),
        }

This would by immediately solved, if Django would provide obj as an argument to inline.get_queryset().

How to implement a get_queryset() of an InlineModelAdmin instance, so that it has access to obj?

class ChildInline(admin.TabularInline):
    ...

    def get_queryset(self, request):
        ???? how to get the parent object?

guettli
  • 25,042
  • 81
  • 346
  • 663

4 Answers4

1

These lines of code are the implementation of how inline admin instance are instantiate in Django

def get_inline_instances(self, request, obj=None):
    inline_instances = []
    for inline_class in self.get_inlines(request, obj):
        inline = inline_class(self.model, self.admin_site)
        if request:
            if not (
                inline.has_view_or_change_permission(request, obj)
                or inline.has_add_permission(request, obj)
                or inline.has_delete_permission(request, obj)
            ):
                continue
            if not inline.has_add_permission(request, obj):
                inline.max_num = 0
        inline_instances.append(inline)
    
    return inline_instances

as you can see there is no obj passed to the inline_class so normally you can't access the parent instance.

Override this function in your parent model's admin class and use assigned attribute 'parent_obj' in your get_queryset method will make it work.

# parent admin class

def get_inline_instances(self, request, obj=None):
    inline_instances = super().get_inline_instances(request, obj)
    for inline_instance in inline_instances:
        inline_instance.parent_obj = obj
    return inline_instances

# inline admin class

def get_queryset(self, request):
    self.parent_obj  # you can now access your parent obj
    
pakawinz
  • 155
  • 5
1

I reffered to https://stackoverflow.com/a/41065115/17524955 and provided one change:

  • replace args to kwargs which contain an object_id

     def get_parent_obj_from_request(self, request):
         resolved = resolve(request.path_info)
         if resolved.kwargs.get('object_id'):
             return self.parent_model.objects.get(pk=resolved.kwargs['object_id'])
         return None
    
     def get_queryset(self, request):
         qs = super().get_queryset(request)
         parent_obj = self.get_parent_obj_from_request(request)
    
NackiE23
  • 21
  • 4
0

I found this dirty hack. In my case Event is the parent-model:

class ChildInline(admin.TabularInline):
    ...

    def get_queryset(self, request):
        # dirty hack to get the parent-object (event).
        # Please fix it and tell me,
        # if you know a better way to get it.
        event_id = request.path_info.split('/')[-2]
        event = Event.objects.get(id=event_id)
        ...
guettli
  • 25,042
  • 81
  • 346
  • 663
0

If i see those questions, i understand, how many people works with django and completely don't understand it.

First.

In the Django admin, I have an Inline, and I would like to filter the list of rows by the parent object.

You don't need to do it in Inline object. Inline is only the helper, who organize creation of InlineFormSet.

And on the first lines of __init__ of your InlineFormSet - it get parent_object and made "filter the list of rows by the parent object" for inline.queryset. (django.forms.models.py, row 904 in Django 4.07)

It means, if you want to filter inline.queryset by parent_id before, it has not any reason to do it twice.

Second.

Please avoid loops. for example - get_inline_instances is good to set parent_object. But not in form of @pakawinz answer. You can do it better:

def get_inline_instances(self, request, obj=None):
    return ((instance, setattr(instance, 'parent_object', obj))[0] for instance in super().get_inline_instances(request, obj))

And you can do it much better:

def get_inlines(self, request, obj):
    """Hook for specifying custom inlines."""
    return (type(inline.__name__, (inline,) {'parent_object': obj}) for inline in super().get_inlines(request, obj))

Second example add parent_object like an attribute to inline_class. it give you possibility to get parent_object in classmethods too.

Third

Your question already has the best possibility to give an inline a parent_object to use it in queryset

def get_formset_kwargs(self, request, obj, inline, prefix):
    inline.parent_obj = obj 
    return super().get_formset_kwargs(request, obj, inline, prefix)

Four

@NackiE23 tells you a better way to get parent_object. Please don't use path.split(), this is not works for add_view in ModelAdmin. in your case:

def get_queryset(self, request):
    ... # your staff
    resolved = resolve(request.path_info) # this is a better way to get it.
    if resolved.kwargs.get('object_id'):
        event = resolved.func.__self__.get_object(request, resolved.kwargs.get('object_id'), to_field=None)  # please check if you don't need to_field
    else:
        event = resolved.func.__self__.model()

Please check, how work your solution not only for change_view, also for add_view too.

Last

I work only with django.admin.contrib more than 7 years. You can find my Talks about django.admin.contrib on PyCon RU 2021, PyCon DE 2022, DjangoCon EU 2022 and, later, on DjangoCon US 2022.

With my experience I think, probably, you do something unnecessary in your code. But i am not sure, therefore i write you some solutions above.

Maxim Danilov
  • 2,472
  • 1
  • 3
  • 8
  • In above text I see comments to other answers. Just out of curiosity, why don't you leave the comments directly on the particular answer? – guettli Oct 03 '22 at 17:20
  • On First is the answer on your question. On second or third or four i write a code. In comment you can not use code-style. And ewery time i change some functionality. – Maxim Danilov Oct 03 '22 at 18:26