1つのdjango ModelFormに複数のモデルがありますか?


96

ModelFormdjangoの単一のモデルに複数のモデルを含めることは可能ですか?プロフィール編集フォームを作成しようとしています。そのため、Userモデル UserProfileモデルのいくつかのフィールドを含める必要があります。現在、私はこのような2つのフォームを使用しています

class UserEditForm(ModelForm):

    class Meta:
        model = User
        fields = ("first_name", "last_name")

class UserProfileForm(ModelForm):

    class Meta:
        model = UserProfile
        fields = ("middle_name", "home_phone", "work_phone", "cell_phone")

これらを1つのフォームに統合する方法はありますか、それともフォームを作成してデータベースの読み込みと保存を処理するだけでよいですか?


回答:


92

1つの<form>html要素内のテンプレートで両方のフォームを表示できます。次に、ビューでフォームを個別に処理します。引き続き使用することができ、form.save()dbのロードと自分自身の保存を処理する必要はありません。

この場合は必要ありませんが、同じフィールド名のフォームを使用する場合はprefix、djangoフォームのkwargを調べてください。(私はそれについてここで質問に答えました)。


これは良いアドバイスですが、適用できない場合もあります。formsetのカスタムモデルフォーム。
タワー

8
複数のフォームとそれらを同じ<form>要素に結合するテンプレートを表示できるクラスベースのビューを作成する簡単な方法は何でしょうか?
jozxyqk

1
しかし、どうやって?通常、に割り当てられるのFormViewは1つだけform_classです。
erikbwork

@erikbworkこの場合、FormViewを使用しないでください。サブクラスTemplateViewを作成し、FormViewと同じロジックを実装しますが、複数のフォームを使用します。
moppag

10

次のコードを使用してみてください。

class CombinedFormBase(forms.Form):
    form_classes = []

    def __init__(self, *args, **kwargs):
        super(CombinedFormBase, self).__init__(*args, **kwargs)
        for f in self.form_classes:
            name = f.__name__.lower()
            setattr(self, name, f(*args, **kwargs))
            form = getattr(self, name)
            self.fields.update(form.fields)
            self.initial.update(form.initial)

    def is_valid(self):
        isValid = True
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            if not form.is_valid():
                isValid = False
        # is_valid will trigger clean method
        # so it should be called after all other forms is_valid are called
        # otherwise clean_data will be empty
        if not super(CombinedFormBase, self).is_valid() :
            isValid = False
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            self.errors.update(form.errors)
        return isValid

    def clean(self):
        cleaned_data = super(CombinedFormBase, self).clean()
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            cleaned_data.update(form.cleaned_data)
        return cleaned_data

使用例:

class ConsumerRegistrationForm(CombinedFormBase):
    form_classes = [RegistrationForm, ConsumerProfileForm]

class RegisterView(FormView):
    template_name = "register.html"
    form_class = ConsumerRegistrationForm

    def form_valid(self, form):
        # some actions...
        return redirect(self.get_success_url())

いくつかの明示的なチェックのため、これは管理者では使用できないようです:admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
WhyNotHugo

どうすれば使用できUpdateViewますか?
Pavel Shlepnev

3

erikbworkと私はどちらも、1つのモデルしかジェネリッククラスベースビューに含めることができないという問題がありました。私はミャオのようにそれに似た方法を見つけましたが、よりモジュール化されています。

Mixinを作成したので、すべての汎用クラスベースビューを使用できます。モデル、フィールド、さらにchild_modelとchild_fieldも定義します。その後、Zachが説明するように、両方のモデルのフィールドをタグでラップできます。

class ChildModelFormMixin: 
    ''' extends ModelFormMixin with the ability to include ChildModelForm '''
    child_model = ""
    child_fields = ()
    child_form_class = None

    def get_child_model(self):
        return self.child_model

    def get_child_fields(self):
        return self.child_fields

    def get_child_form(self):
        if not self.child_form_class:
            self.child_form_class = model_forms.modelform_factory(self.get_child_model(), fields=self.get_child_fields())
        return self.child_form_class(**self.get_form_kwargs())

    def get_context_data(self, **kwargs):
        if 'child_form' not in kwargs:
            kwargs['child_form'] = self.get_child_form()
        return super().get_context_data(**kwargs)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        child_form = self.get_child_form()

        # check if both forms are valid
        form_valid = form.is_valid()
        child_form_valid = child_form.is_valid()

        if form_valid and child_form_valid:
            return self.form_valid(form, child_form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, child_form):
        self.object = form.save()
        save_child_form = child_form.save(commit=False)
        save_child_form.course_key = self.object
        save_child_form.save()

        return HttpResponseRedirect(self.get_success_url())

使用例:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_fields = ('payment_token', 'cart',)

またはModelFormClassで:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_form_class = ConsumerProfileForm

できました。それが誰かを助けることを願っています。


この中でsave_child_form.course_key = self.object、何.course_keyですか?
アダムスター2017

私は、course_keyが関連モデルであると思います。私の場合、それは、backrefであるUserProfile.userのように「user」です。おそらく、再利用可能なミックスインである場合、そのフィールド名はカスタマイズ可能であるべきです。しかし、まだ別の問題があり、子フォームに実際に初期データが入力されていません。Userのすべてのフィールドは事前入力されていますが、UserProfileには入力されていません。最初に修正する必要があるかもしれません。
robvdl

子フォームにデータが入力されない理由は、get_child_formメソッドで呼び出しますreturn self.child_form_class(**self.get_form_kwargs())が、で間違ったモデルインスタンスを取得kwargs['instance']するためです。たとえば、インスタンスはメインモデルであり、子モデルではありません。修正するには、まずkwargsを変数に保存してから、を呼び出す前に正しいモデルインスタンスでkwargs = self.get_form_kwargs()更新する必要がありkwargs['initial']ますreturn self.child_form_class(**kwargs)。私の場合、kwargs['instance'] = kwargs['instance'].profileこれが理にかなっている場合でした。
robvdl

残念ながら、保存時に、2つの場所でクラッシュします。1つはform_validにself.objectがまだないため、AttributeErrorがスローされ、別の場所のインスタンスはそこにありません。このソリューションが投稿される前に完全にテストされたかどうかはわかりませんので、CombinedFormBaseを使用して他の回答に進む方が良いかもしれません。
robvdl

1
なにmodel_forms
Granny Aching

2

おそらくインラインフォームセットを確認する必要があります。インラインフォームセットは、モデルが外部キーによって関連付けられている場合に使用されます。


1
インラインフォームセットは、1対多の関係で作業する必要がある場合に使用されます。従業員を追加する会社など。2つのテーブルを1つの単一のフォームに結合しようとしています。1対1の関係です。
Jason Webb

インラインフォームセットの使用は機能しますが、理想的とは言えません。リレーションを処理するモデルを作成し、単一のフォームを使用することもできます。stackoverflow.com/questions/2770810/…で提案されているように、2つのフォームを持つ単一のページがあるだけでうまくいきます。
John Percival Hackworth、

2

同様の問題については、ここ私の答えを確認できます。

登録とユーザープロファイルを1つのフォームに組み合わせる方法について説明しますが、ModelFormの組み合わせに一般化できます。


0

私のプロジェクトでは、django betterformsMultiFormとMultiModelFormを使用しました。ただし、コードは改善できます。たとえば、それは3. +でサポートされていないdjango.sixに依存していますが、これらはすべて簡単に修正できます

この質問はStackOverflowで 出てきたので、これに対処するための標準化された方法を見つける時がきたと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.