Accessing parent model instance from modelform of admin inline

前端 未结 4 742
天命终不由人
天命终不由人 2020-12-24 07:59

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

class MyChildInline(admin.TabularInline):
    model = MyChildModel
           


        
相关标签:
4条回答
  • 2020-12-24 08:24

    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:

    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
    
    0 讨论(0)
  • 2020-12-24 08:30

    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.

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

    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:

    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.

    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:

    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.

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

    0 讨论(0)
  • 2020-12-24 08:39

    I'm using Django 1.10 and it works for me:
    Create a FormSet and put the parent object into 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

    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
    
    0 讨论(0)
  • 2020-12-24 08:43

    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__

    0 讨论(0)
提交回复
热议问题