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