Beberapa Model dalam satu django ModelForm?

98

Apakah mungkin untuk memasukkan beberapa model ke dalam satu ModelFormdi django? Saya mencoba membuat formulir edit profil. Jadi saya perlu menyertakan beberapa bidang dari model User dan model UserProfile. Saat ini saya menggunakan 2 bentuk seperti ini

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")

Apakah ada cara untuk menggabungkan ini ke dalam satu formulir atau apakah saya hanya perlu membuat formulir dan menangani pemuatan db dan menyimpan sendiri?

Jason Webb
sumber
Kemungkinan duplikat dari Django: beberapa model dalam satu templat menggunakan formulir
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

93

Anda bisa menampilkan kedua formulir di templat di dalam satu <form>elemen html. Kemudian proses formulir secara terpisah dalam tampilan. Anda masih dapat menggunakan form.save()dan tidak perlu memproses db memuat dan menyimpan sendiri.

Dalam hal ini Anda tidak memerlukannya, tetapi jika Anda akan menggunakan formulir dengan nama bidang yang sama, lihat prefixkwarg untuk formulir django. (Saya menjawab pertanyaan tentang itu di sini ).

Zach
sumber
Ini adalah nasihat yang bagus, tetapi ada beberapa kasus hal ini tidak berlaku, mis. formulir model kustom untuk formset.
Wtower
8
Apa cara mudah untuk membuat tampilan berbasis kelas mampu memperlihatkan lebih dari satu formulir dan templat yang kemudian menggabungkannya ke dalam <form>elemen yang sama ?
jozxyqk
1
Tapi bagaimana caranya? Biasanya FormViewhanya memiliki satu yang form_classditugaskan padanya.
erikbwork
@erikbwork Anda tidak boleh menggunakan FormView untuk kasus ini. Cukup Subclass TemplateViewdan implementasikan logika yang sama dengan FormView, tetapi dengan berbagai bentuk.
moppag
10

Anda dapat mencoba menggunakan potongan kode ini:

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

Contoh Penggunaan:

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())
Miao ZhiCheng
sumber
Sepertinya ini tidak dapat digunakan di admin karena beberapa pemeriksaan eksplisit:admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
WhyNotHugo
Bagaimana saya bisa menggunakannya dengan UpdateView?
Pavel Shlepnev
3

erikbwork dan saya memiliki masalah bahwa seseorang hanya dapat memasukkan satu model ke dalam Tampilan Berbasis Kelas yang umum. Saya menemukan cara serupa untuk mendekatinya seperti Miao, tetapi lebih modular.

Saya menulis Mixin sehingga Anda dapat menggunakan semua Tampilan Berbasis Kelas generik. Tentukan model, kolom, dan sekarang juga child_model dan child_field - lalu Anda dapat membungkus kolom dari kedua model dalam tag seperti yang dijelaskan 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())

Contoh Penggunaan:

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

Atau dengan ModelFormClass:

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

Selesai. Harapan yang membantu seseorang.

LGG
sumber
Dalam hal ini save_child_form.course_key = self.object, apakah .course_key?
Adam Starrh
Saya pikir course_key adalah model terkait, dalam kasus saya itu adalah "user" seperti di UserProfile.user yang merupakan backref, mungkin nama field itu harus disesuaikan jika itu menjadi mixin yang dapat digunakan kembali. Tapi saya masih mengalami masalah lain di mana formulir turunan tidak benar-benar diisi dengan data awal, semua bidang dari Pengguna sudah terisi sebelumnya tetapi tidak untuk ProfilPengguna. Saya mungkin harus memperbaikinya dulu.
robvdl
Masalah mengapa bentuk anak tidak terisi adalah karena dalam metode get_child_form, ia memanggil return self.child_form_class(**self.get_form_kwargs())tetapi mendapatkan contoh model yang salah kwargs['instance'], misalnya contoh adalah model utama dan bukan model anak. Untuk memperbaikinya, Anda perlu menyimpan kwargs ke dalam variabel terlebih dahulu kwargs = self.get_form_kwargs()kemudian memperbarui kwargs['initial']dengan contoh model yang benar sebelum memanggil return self.child_form_class(**kwargs). Dalam kasus saya, kwargs['instance'] = kwargs['instance'].profilejika ini masuk akal.
robvdl
Sayangnya saat menyimpannya masih akan macet di dua tempat, satu di mana self.object belum ada di form_valid, jadi itu melempar AttributeError, dan contoh tempat lain tidak ada. Saya tidak yakin apakah solusi ini telah diuji sepenuhnya sebelum diposting, jadi mungkin lebih baik menggunakan jawaban lain menggunakan CombinedFormBase.
robvdl
1
Apa model_forms?
Granny Aching
2

Anda mungkin harus melihat formulir Inline . Kumpulan formulir sebaris digunakan saat model Anda dihubungkan dengan kunci asing.

John Percival Hackworth
sumber
1
Formulir sebaris digunakan saat Anda perlu bekerja dengan hubungan satu ke banyak. Semisal perusahaan tempat Anda menambah karyawan. Saya mencoba menggabungkan 2 tabel menjadi satu bentuk tunggal. Itu adalah hubungan satu lawan satu.
Jason Webb
Penggunaan kumpulan formulir Inline akan berhasil, tetapi kemungkinan besar kurang dari ideal. Anda juga bisa membuat Model yang menangani relasi untuk Anda, lalu menggunakan satu formulir. Hanya memiliki satu halaman dengan 2 formulir seperti yang disarankan di stackoverflow.com/questions/2770810/… akan berfungsi.
John Percival Hackworth
2

Anda dapat memeriksa jawaban saya di sini untuk masalah serupa.

Ini berbicara tentang cara menggabungkan pendaftaran dan profil pengguna ke dalam satu bentuk, tetapi dapat digeneralisasikan ke kombinasi ModelForm apa pun.

Mitar
sumber
0

Saya menggunakan Django betterforms 's beraneka ragam dan MultiModelForm dalam proyek saya. Namun, kodenya dapat ditingkatkan. Misalnya, ini bergantung pada django.six, yang tidak didukung oleh 3. +, tetapi semua ini dapat diperbaiki dengan mudah

Pertanyaan ini telah muncul beberapa kali di StackOverflow, jadi saya pikir inilah saatnya untuk menemukan cara standar untuk mengatasi hal ini.

J Eti
sumber