InlineModelAdmin.get_queryset() как получить доступ к родительскому объекту?

В админке Django у меня есть Inline, и я хотел бы отфильтровать список строк по родительскому объекту.

Я могу переопределить get_queryset(request) в моем Inline, но у меня нет доступа к родительскому объекту.

Этот фрагмент взят из файла options.py в Django:

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

Это было бы сразу решено, если бы Django предоставил obj в качестве аргумента inline.get_queryset().

Как реализовать get_queryset() экземпляра InlineModelAdmin, чтобы он имел доступ к obj?

Я нашел этот грязный хак. В моем случае Event является родительской моделью:

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)
        ...

Эти строки кода представляют собой реализацию того, как инстанцируются экземпляры встроенного администратора в 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

Как вы видите, в inline_class не передан obj, поэтому обычно вы не можете получить доступ к родительскому экземпляру.

Переопределение этой функции в классе admin вашей родительской модели и использование назначенного атрибута 'parent_obj' в методе get_queryset заставит ее работать.

# 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
    

Я сослался на https://stackoverflow.com/a/41065115/17524955 и предоставил одно изменение:

  • заменить args на kwargs, которые содержат 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)
    

Когда я вижу эти вопросы, я понимаю, как много людей работает с django и совершенно не понимает его.

Первый.

В админке Django у меня есть Inline, и я хотел бы отфильтровать список строк по родительскому объекту.

Вам не нужно делать это в объекте Inline. Inline - это только хелпер, который организует создание InlineFormSet.

На первых строках __init__ вашего InlineFormSet - получилось parent_object и сделано "фильтровать список строк по родительскому объекту" для inline.queryset. (django.forms.models.py, row 904 в Django 4.07)

Значит, если вы хотите отфильтровать inline.queryset по parent_id раньше, то нет смысла делать это дважды.

Секунда.

Пожалуйста, избегайте циклов. например - get_inline_instances хорош для установки parent_object. Но не в форме ответа @pakawinz. Вы можете сделать это лучше:

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))

А вы можете сделать это гораздо лучше:

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))

Второй пример добавьте parent_object как атрибут к inline_class. Это даст вам возможность получить parent_object в classmethods также

Третий

Ваш вопрос уже имеет наилучшую возможность дать инлайну родительский_объект, чтобы использовать его в queryset

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

Четыре

@NackiE23 подскажет вам лучший способ получить parent_object. Пожалуйста, не используйте path.split(), это не работает для add_view в ModelAdmin. в вашем случае:

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()

Пожалуйста, проверьте, как работает ваше решение не только для change_view, но и для add_view тоже.

Последний

Я работаю только с django.admin.contrib более 7 лет. Вы можете найти мои выступления о django.admin.contrib на PyCon RU 2021, PyCon DE 2022, DjangoCon EU 2022 и, позже, на DjangoCon US 2022.

По своему опыту я думаю, что, возможно, вы делаете что-то лишнее в своем коде. Но я не уверен, поэтому я написал вам несколько решений выше.

Вернуться на верх