Подтвердить что ты не робот

Доступ к экземпляру родительской модели из 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 и т.д.), но я не могу найти способ доступа к родительскому объекту, к которому привязана табличная строка. Есть ли способ сделать это?

4b9b3361

Ответ 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