Доступ к экземпляру родительской модели из формы модели встроенного администратора - PullRequest
16 голосов
/ 24 февраля 2012

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

Ответы [ 4 ]

24 голосов
/ 21 августа 2012

Обновление: Начиная с 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_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, следовательно, ваш код формы должен принимать параметры по выбору.

4 голосов
/ 22 марта 2017

Я использую 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
1 голос
/ 23 сентября 2016

Если немного расширить ответ Ильвара, если интересующее поле формы построено из поля модели, вы можете использовать следующую конструкцию, чтобы применить к нему пользовательское поведение:

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 голос
/ 24 февраля 2012

AdminModel имеет несколько методов, таких как get_formsets .Он получает объект и возвращает набор форм.Я думаю, что вы можете добавить некоторую информацию о родительском объекте в классы этого набора форм и использовать ее позже в __init __

набора форм.
...