从管理内联的modelform访问父模型实例

时间:2022-03-29 15:52:31

I'm using a TabularInline in Django's admin, configured to show one extra blank form.

我正在Django的管理中使用一个TabularInline,它被配置为显示一个额外的空白表单。

class MyChildInline(admin.TabularInline):
    model = MyChildModel
    form = MyChildInlineForm
    extra = 1

The model looks like MyParentModel->MyChildModel->MyInlineForm.

这个模型看起来像MyParentModel->MyChildModel->MyInlineForm。

I'm using a custom form so I can dynamically lookup values and populate choices in a field. e.g.

我使用自定义表单,这样我就可以动态查找值并填充字段中的选项。如。

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]

This works fine for the inline records bound to an actual record, but for the extra blank form, it doesn't display any values in my choice field, since it doesn't have any record id and there can't lookup the associated MyParentModel record.

这对绑定到实际记录的内联记录有效,但是对于额外的空白表单,它在我的选择字段中没有显示任何值,因为它没有任何记录id,也没有查找相关的MyParentModel记录。

I've inspected all the values I could find (args, kwargs, self.data, self.instance, etc) but I can't find any way to access the parent object the tabular inline is bound to. Is there any way to do this?

我检查了所有我能找到的值(args, kwargs, self)数据,自我。但是我找不到任何方法来访问表列内联绑定到的父对象。有什么办法吗?

4 个解决方案

#1


23  

Update: As of Django 1.9, there is a def get_form_kwargs(self, index) method in the BaseFormSet class. Hence, overriding that passes the data to the form.

更新:对于Django 1.9,在BaseFormSet类中有一个def get_form_kwargs(self, index)方法。因此,重写将数据传递给窗体。

This would be the Python 3 / Django 1.9+ version:

这将是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

For Django 1.8 and below:

Django 1.8及以下版本:

To pass a value of a formset to the individual forms, you'd have to see how they are constructed. An editor/IDE with "jump to definition" really helps here to dive into the ModelAdmin code, and learn about the inlineformset_factory and it's BaseInlineFormSet class.

要将表单集的值传递给单个表单,您必须了解它们是如何构造的。一个“跳转到定义”的编辑器/IDE确实有助于深入到ModelAdmin代码中,并了解inlineformset_factory和它的BaseInlineFormSet类。

From there you'll find that the form is constructed in _construct_form() and you can override that to pass extra parameters. It will likely look something like this:

从那里您将发现该表单是在_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

Yes, this involves a private _construct_form function.

是的,这涉及一个私有的_construct_form函数。

update Note: This doesn't cover the empty_form, hence your form code needs to accept the parameters optionally.

更新说明:这并不包括empty_form,因此您的表单代码需要接受参数。

#2


3  

I'm using Django 1.10 and it works for me:
Create a FormSet and put the parent object into kwargs:

我正在使用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

Create a Form and pop an atribute before super called

创建一个表单,在super call之前弹出一个atribute

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

Put that in the inline admin:

把它放在内联管理员:

class MyModelInline(admin.StackedInline):

    model = MyModel
    fields = ('my_fields', )
    form = MyFrom
    formset = MyFormSet

#3


1  

AdminModel has some methods like get_formsets. It receives an object and returns a bunch of formsets. I think you can add some info about parent object to that formset classes and use it later in formset's __init__

AdminModel有一些方法,如get_formsets。它接收一个对象并返回一堆格式集。我认为您可以在formset的__init__中添加一些关于父对象的信息,并将其添加到formset类中。

#4


1  

Expanding on ilvar's answer a bit, If the form field of interest is constructed from a model field, you can use the following construction to apply custom behavior to it:

扩展一下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

#1


23  

Update: As of Django 1.9, there is a def get_form_kwargs(self, index) method in the BaseFormSet class. Hence, overriding that passes the data to the form.

更新:对于Django 1.9,在BaseFormSet类中有一个def get_form_kwargs(self, index)方法。因此,重写将数据传递给窗体。

This would be the Python 3 / Django 1.9+ version:

这将是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

For Django 1.8 and below:

Django 1.8及以下版本:

To pass a value of a formset to the individual forms, you'd have to see how they are constructed. An editor/IDE with "jump to definition" really helps here to dive into the ModelAdmin code, and learn about the inlineformset_factory and it's BaseInlineFormSet class.

要将表单集的值传递给单个表单,您必须了解它们是如何构造的。一个“跳转到定义”的编辑器/IDE确实有助于深入到ModelAdmin代码中,并了解inlineformset_factory和它的BaseInlineFormSet类。

From there you'll find that the form is constructed in _construct_form() and you can override that to pass extra parameters. It will likely look something like this:

从那里您将发现该表单是在_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

Yes, this involves a private _construct_form function.

是的,这涉及一个私有的_construct_form函数。

update Note: This doesn't cover the empty_form, hence your form code needs to accept the parameters optionally.

更新说明:这并不包括empty_form,因此您的表单代码需要接受参数。

#2


3  

I'm using Django 1.10 and it works for me:
Create a FormSet and put the parent object into kwargs:

我正在使用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

Create a Form and pop an atribute before super called

创建一个表单,在super call之前弹出一个atribute

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

Put that in the inline admin:

把它放在内联管理员:

class MyModelInline(admin.StackedInline):

    model = MyModel
    fields = ('my_fields', )
    form = MyFrom
    formset = MyFormSet

#3


1  

AdminModel has some methods like get_formsets. It receives an object and returns a bunch of formsets. I think you can add some info about parent object to that formset classes and use it later in formset's __init__

AdminModel有一些方法,如get_formsets。它接收一个对象并返回一堆格式集。我认为您可以在formset的__init__中添加一些关于父对象的信息,并将其添加到formset类中。

#4


1  

Expanding on ilvar's answer a bit, If the form field of interest is constructed from a model field, you can use the following construction to apply custom behavior to it:

扩展一下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