단일 django ModelForm에 여러 모델이 있습니까?


96

ModelFormDjango 에서 단일 모델에 여러 모델을 포함 할 수 있습니까? 프로필 편집 양식을 만들려고합니다. 따라서 User 모델 UserProfile 모델 의 일부 필드를 포함해야합니다 . 현재 나는 이와 같은 두 가지 양식을 사용하고 있습니다.

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

이를 하나의 양식으로 통합하는 방법이 있습니까? 아니면 양식을 작성하고 db로드 및 저장을 직접 처리해야합니까?


답변:


92

하나의 <form>html 요소 내의 템플릿에 두 양식을 모두 표시 할 수 있습니다 . 그런 다음보기에서 양식을 별도로 처리하십시오. 여전히 사용할 수 있으며 form.save()db로드 및 저장을 처리 할 필요가 없습니다.

이 경우에는 필요하지 않지만 동일한 필드 이름을 가진 양식을 사용하려면 prefixdjango 양식 에 대한 kwarg를 살펴보십시오 . ( 여기 에 대한 질문에 답했습니다 ).


이것은 좋은 조언이지만 적용되지 않는 경우가 있습니다. formset에 대한 사용자 정의 모델 양식.
Wtower

8
둘 이상의 폼과 템플릿을 표시 할 수있는 클래스 기반 뷰를 만들고이를 동일한 <form>요소 로 결합하는 간단한 방법은 무엇입니까 ?
jozxyqk

1
하지만 어떻게? 일반적으로 FormView하나만 form_class할당됩니다.
erikbwork

@erikbwork이 경우 FormView를 사용하지 마십시오. TemplateViewFormView와 동일한 논리를 하위 클래스 로 구현하고 여러 양식으로 구현하십시오.
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와 저 모두 일반적인 클래스 기반 뷰에 하나의 모델 만 포함 할 수 있다는 문제가있었습니다. 나는 Miao처럼 접근하는 비슷한 방법을 찾았지만 더 모듈화되었습니다.

모든 일반 클래스 기반 뷰를 사용할 수 있도록 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입니까?
Adam Starrh

내 경우에는 backref 인 UserProfile.user에서와 같이 "user"인 course_key가 관련 모델이라고 생각합니다. 재사용 가능한 믹스 인이라면 해당 필드 이름을 사용자 정의 할 수 있어야합니다. 그러나 하위 양식이 실제로 초기 데이터로 채워지지 않고 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

불행히도 저장시 여전히 두 곳에서 충돌 할 것입니다. 하나는 self.object가 아직 form_valid에 존재하지 않으므로 AttributeError를 던지고 다른 장소 인스턴스는 없습니다. 이 솔루션이 게시되기 전에 완전히 테스트되었는지 확실하지 않으므로 CombinedFormBase를 사용하여 다른 답변을 사용하는 것이 더 나을 수 있습니다.
robvdl

1
무엇입니까 model_forms?
할머니 아프다

2

인라인 양식 집합을 살펴보아야 할 것 입니다. 인라인 양식 집합은 모델이 외래 키로 관련 될 때 사용됩니다.


1
인라인 양식 집합은 일대 다 관계로 작업해야 할 때 사용됩니다. 직원을 추가하는 회사 등. 2 개의 테이블을 하나의 단일 형태로 결합하려고합니다. 일대일 관계입니다.
Jason Webb

인라인 폼셋의 사용은 작동하지만 이상적이지 않습니다. 관계를 처리하는 모델을 만든 다음 단일 양식을 사용할 수도 있습니다. stackoverflow.com/questions/2770810/… 에서 제안 된대로 2 개의 양식이있는 단일 페이지 만 있으면 작동합니다.
John Percival Hackworth

2

여기에서 내 대답을 확인할 수 있습니다.비슷한 문제에 대해 에서 .

등록 및 사용자 프로필을 하나의 양식으로 결합하는 방법에 대해 설명하지만 모든 ModelForm 조합으로 일반화 할 수 있습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.