【问题标题】:Accessing parent model instance from modelform of admin inline从 admin inline 的 modelform 访问父模型实例
【发布时间】:2012-03-14 10:19:16
【问题描述】:

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

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]

这适用于绑定到实际记录的内联记录,但对于额外的空白表单,它不会在我的选择字段中显示任何值,因为它没有任何记录 ID 并且无法查找关联的 MyParentModel 记录。

我检查了我能找到的所有值(args、kwargs、self.data、self.instance 等),但我找不到任何方法来访问表格内联绑定到的父对象。有没有办法做到这一点?

【问题讨论】:

    标签: python django django-models django-forms


    【解决方案1】:

    我使用的是 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
    

    【讨论】:

    • 很高兴看到 Django 1.9+ 解决方案 :-) 而不是 self.form_kwargs.copy() 我建议改为调用 super()
    • 2 个问题:1)什么是 FormFieldMetaForm?不应该是 super(MyForm,self) 吗? 2)在MyForm.__init__()中,实际使用parent时,应该在调用super().__init__()之前还是之后?
    • 我上述问题的答案是 1) '是',和 2) '之后'。我正在编辑这个答案以反映这一点
    • 适用于 Django 1.11!
    【解决方案2】:

    更新: 从 Django 1.9 开始,BaseFormSet 类中有一个 def get_form_kwargs(self, index) 方法。因此,覆盖将数据传递给表单。

    这将是 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_factoryBaseInlineFormSet 类。

    从那里您会发现表单是在_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 函数。

    更新注意:这不包括empty_form,因此您的表单代码需要可选地接受参数。

    【讨论】:

    • +1,虽然我不确定@Cerin 到底想解决什么问题,但大多数时候使用通用内联并使用表单集实例检查内容类型和对象ID 应该更容易。
    • 我试过这个,但是我在 MyForm 的行中得到了一个 KeyError for 'parent_object' 你试图弹出 parent_object 值。
    • 完美运行!确保在调用 super 之前弹出,并跳过空表单。
    • 在 Django 1.11 中再次测试。完美运行。
    • @vdboor,我希望你不介意我的编辑。深入研究代码,我想出了如何支持empty_form
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      AdminModel 有一些方法,例如get_formsets。它接收一个对象并返回一堆表单集。我认为您可以将有关父对象的一些信息添加到该表单集类中,然后在表单集的 __init__ 中使用它

      【讨论】:

        猜你喜欢
        • 2015-03-23
        • 2015-11-15
        • 2012-10-28
        • 1970-01-01
        • 2017-08-22
        • 1970-01-01
        • 1970-01-01
        • 2010-10-31
        • 1970-01-01
        相关资源
        最近更新 更多