หลายรุ่นใน django ModelForm เดียว?


99

เป็นไปได้ไหมที่จะมีหลายรุ่นรวมอยู่ModelFormใน django เครื่องเดียว? ฉันกำลังพยายามสร้างแบบฟอร์มแก้ไขโปรไฟล์ ดังนั้นฉันต้องรวมฟิลด์บางฟิลด์จากโมเดลผู้ใช้และโมเดล 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")

มีวิธีการรวมสิ่งเหล่านี้เป็นรูปแบบเดียวหรือไม่หรือฉันเพียงแค่ต้องสร้างฟอร์มและจัดการกับการโหลดฐานข้อมูลและบันทึกตัวเอง


คำตอบ:


94

คุณสามารถแสดงทั้งสองรูปแบบในเทมเพลตภายใน<form>องค์ประกอบ html เดียว จากนั้นเพียงแค่ประมวลผลแบบฟอร์มแยกกันในมุมมอง คุณจะยังใช้งานform.save()ได้และไม่ต้องดำเนินการโหลดฐานข้อมูลและบันทึกด้วยตัวคุณเอง

ในกรณีนี้คุณไม่จำเป็นต้องใช้ แต่ถ้าคุณจะใช้แบบฟอร์มที่มีชื่อฟิลด์เดียวกันให้ดูที่prefixkwarg สำหรับแบบฟอร์ม django (ฉันตอบคำถามเกี่ยวกับเรื่องนี้ที่นี่ )


นี่เป็นคำแนะนำที่ดี แต่มีบางกรณีที่ใช้ไม่ได้เช่น แบบฟอร์มที่กำหนดเองสำหรับชุดฟอร์ม
Wtower

8
อะไรคือวิธีที่ตรงไปตรงมาในการสร้างมุมมองตามคลาสที่สามารถแสดงมากกว่าหนึ่งฟอร์มและเทมเพลตที่รวมเข้าเป็น<form>องค์ประกอบเดียวกันได้
jozxyqk

1
แต่อย่างไร? โดยปกติแล้วจะมีFormViewเพียงรายการเดียวที่form_classกำหนดให้
erikbwork

@erikbwork คุณไม่ควรใช้ FormView สำหรับกรณีนี้ เพียง Subclass 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

4

erikbwork และฉันทั้งคู่มีปัญหาที่สามารถรวมโมเดลได้เพียงรุ่นเดียวในมุมมองตามคลาสทั่วไป ฉันพบวิธีที่คล้ายกันในการเข้าหามันเหมือน Miao แต่เป็นแบบแยกส่วนมากกว่า

ฉันเขียน Mixin เพื่อให้คุณสามารถใช้ Class Based Views ทั่วไปได้ทั้งหมด กำหนดโมเดลฟิลด์และตอนนี้ยังมี 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อะไร?
Adam Starrh

ฉันคิดว่า course_key เป็นโมเดลที่เกี่ยวข้องในกรณีของฉันคือ "ผู้ใช้" เช่นเดียวกับใน UserProfile.user ซึ่งเป็น backref บางทีชื่อฟิลด์นั้นควรจะปรับแต่งได้หากเป็นมิกซ์อินที่ใช้ซ้ำได้ แต่ฉันยังคงมีปัญหาอื่นที่ฟอร์มลูกไม่ได้รับการเติมเต็มด้วยข้อมูลเริ่มต้นจริงฟิลด์ทั้งหมดจากผู้ใช้จะถูกเติมไว้ล่วงหน้า แต่ไม่ใช่สำหรับ 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

ขออภัยในการบันทึกมันจะยังคงขัดข้องในสองที่โดยที่ self.object ยังไม่มีใน form_valid ดังนั้นมันจึงพ่น AttributeError และอินสแตนซ์ของที่อื่นไม่อยู่ที่นั่น ฉันไม่แน่ใจว่าโซลูชันนี้ได้รับการทดสอบอย่างสมบูรณ์ก่อนที่จะโพสต์หรือไม่ดังนั้นจึงควรใช้คำตอบอื่นโดยใช้ CombinedFormBase
robvdl

1
คืออะไรmodel_forms?
Granny

2

คุณอาจจะดูที่formsets Inline ชุดฟอร์มแบบอินไลน์ใช้เมื่อโมเดลของคุณเกี่ยวข้องกันด้วยคีย์นอก


1
ชุดฟอร์มแบบอินไลน์ใช้เมื่อคุณต้องการทำงานกับความสัมพันธ์แบบหนึ่งต่อหลาย เช่น บริษัท ที่คุณเพิ่มพนักงาน ฉันกำลังพยายามรวม 2 ตารางเป็นรูปแบบเดียว มันเป็นความสัมพันธ์แบบหนึ่งต่อหนึ่ง
Jason Webb

การใช้ Inline formet จะได้ผล แต่น่าจะน้อยกว่าอุดมคติ คุณยังสามารถสร้างแบบจำลองที่จัดการความสัมพันธ์ให้คุณจากนั้นใช้แบบฟอร์มเดียว แค่มีหน้าเดียว 2 แบบตามที่แนะนำในstackoverflow.com/questions/2770810/…ก็ใช้ได้
John Percival Hackworth

2

คุณสามารถตรวจสอบคำตอบของฉันได้ที่นี่สำหรับปัญหาที่คล้ายกัน

พูดถึงวิธีการรวมการลงทะเบียนและโปรไฟล์ผู้ใช้ไว้ในรูปแบบเดียว แต่สามารถนำไปใช้กับชุดค่าผสม ModelForm ใดก็ได้


1

ผมใช้Django betterforms 's หลายชนิดและ MultiModelFormในโครงการของฉัน แม้ว่าโค้ดสามารถปรับปรุงได้ ตัวอย่างเช่นขึ้นอยู่กับ django.six ซึ่งไม่รองรับโดย 3. + แต่สิ่งเหล่านี้สามารถแก้ไขได้อย่างง่ายดาย

คำถามนี้เกิดขึ้นหลาย ครั้งใน StackOverflow ดังนั้นฉันคิดว่าถึงเวลาแล้วที่จะหาวิธีจัดการกับปัญหานี้ที่เป็นมาตรฐาน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.