Доступ к экземпляру родительской модели из modelform admin inline
Я использую TabularInline в администраторе Django, настроенный для отображения одной дополнительной пустой формы.
class MyChildInline(admin.TabularInline):
model = MyChildModel
form = MyChildInlineForm
extra = 1
Модель выглядит как MyParentModel- > MyChildModel- > MyInlineForm.
Я использую настраиваемую форму, чтобы я мог динамически искать значения и заполнять варианты в поле. например
class MyChildInlineForm(ModelForm):
my_choice_field = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(MyChildInlineForm, self).__init__(*args, **kwargs)
# Lookup ID of parent model.
parent_id = None
if "parent_id" in kwargs:
parent_id = kwargs.pop("parent_id")
elif self.instance.parent_id:
parent_id = self.instance.parent_id
elif self.is_bound:
parent_id = self.data['%s-parent'% self.prefix]
if parent_id:
parent = MyParentModel.objects.get(id=parent_id)
if rev:
qs = parent.get_choices()
self.fields['my_choice_field'].choices = [(r.name,r.value) for r in qs]
Это отлично подходит для встроенных записей, привязанных к фактической записи, но для дополнительной пустой формы она не отображает никаких значений в моем поле выбора, так как у нее нет идентификатора записи, и нет возможности поиска связанную запись MyParentModel.
Я проверил все значения, которые я смог найти (args, kwargs, self.data, self.instance и т.д.), но я не могу найти способ доступа к родительскому объекту, к которому привязана табличная строка. Есть ли способ сделать это?
Ответы
Ответ 1
Обновление: В Django 1.9 существует метод def get_form_kwargs(self, index)
в классе BaseFormSet
. Следовательно, переопределение, которое передает данные в форму.
Это будет версия Python 3/Django 1.9+:
class MyFormSet(BaseInlineFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
kwargs['parent_object'] = self.instance
return kwargs
class MyForm(forms.ModelForm):
def __init__(self, *args, parent_object, **kwargs):
self.parent_object = parent_object
super(MyForm, self).__init__(*args, **kwargs)
class MyChildInline(admin.TabularInline):
formset = MyFormSet
form = MyForm
Для Django 1.8 и ниже:
Чтобы передать значение набора форм в отдельные формы, вам нужно будет увидеть, как они построены. Редактор /IDE с "прыжком к определению" действительно помогает здесь погрузиться в код ModelAdmin
и узнать о классе inlineformset_factory
и BaseInlineFormSet
.
Оттуда вы обнаружите, что форма построена в _construct_form()
, и вы можете переопределить это, чтобы передать дополнительные параметры. Вероятно, это выглядит примерно так:
class MyFormSet(BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
kwargs['parent_object'] = self.instance
return super(MyFormSet, self)._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
parent_object=self.instance,
)
self.add_fields(form, None)
return form
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.parent_object = kwargs.pop('parent_object', None)
super(MyForm, self).__init__(*args, **kwargs)
class MyChildInline(admin.TabularInline):
formset = MyFormSet
form = MyForm
Да, это связано с частной функцией _construct_form
.
update Примечание. Это не распространяется на empty_form
, поэтому ваш код формы должен принимать параметры необязательно.
Ответ 2
Я использую Django 1.10, и он работает для меня:
Создайте FormSet и поместите родительский объект в kwargs:
class MyFormSet(BaseInlineFormSet):
def get_form_kwargs(self, index):
kwargs = super(MyFormSet, self).get_form_kwargs(index)
kwargs.update({'parent': self.instance})
return kwargs
Создайте Форму и вставьте атрибут перед super
вызываемым
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
parent = kwargs.pop('parent')
super(MyForm, self).__init__(*args, **kwargs)
# do whatever you need to with parent
Поместите это во встроенный админ:
class MyModelInline(admin.StackedInline):
model = MyModel
fields = ('my_fields', )
form = MyFrom
formset = MyFormSet
Ответ 3
В AdminModel есть несколько методов, например get_formsets. Он получает объект и возвращает кучу наборов форм. Я думаю, вы можете добавить некоторую информацию о родительском объекте к этим классам форм и использовать его позже в formset __init __
Ответ 4
Расширение на ilvar немного ответит. Если интересующее поле формы построено из поля модели, вы можете использовать следующую конструкцию для применения к ней пользовательского поведения:
class MyChildInline(admin.TabularInline):
model = MyChildModel
extra = 1
def get_formset(self, request, parent=None, **kwargs):
def formfield_callback(db_field):
"""
Constructor of the formfield given the model field.
"""
formfield = self.formfield_for_dbfield(db_field, request=request)
if db_field.name == 'my_choice_field' and parent is not None:
formfield.choices = parent.get_choices()
return formfield
return super(MyChildInline, self).get_formset(
request, obj=obj, formfield_callback=formfield_callback, **kwargs)
return result