Tek bir django ModelForm'da birden çok Model?


99

ModelFormDjango'da bir single'a birden fazla model dahil etmek mümkün mü ? Profil düzenleme formu oluşturmaya çalışıyorum. Bu yüzden User modelinden ve UserProfile modelinden bazı alanları eklemem gerekiyor. Şu anda bunun gibi 2 form kullanıyorum

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

Bunları tek bir formda birleştirmenin bir yolu var mı yoksa sadece bir form oluşturmam ve db yükleme ve kaydetme işlemlerini kendim yapmam mı gerekiyor?


Yanıtlar:


94

Bir <form>html öğesinin içindeki şablonda her iki formu da gösterebilirsiniz . Ardından, görünümde formları ayrı ayrı işleyin. Yine de kullanabileceksiniz form.save()ve db yüklemesini ve kendinizi kaydetmeyi işlemeniz gerekmeyecek.

Bu durumda buna ihtiyacınız olmamalıdır, ancak aynı alan adlarına sahip formları prefixkullanacaksanız, django formları için kwarg'a bakın. (Bununla ilgili bir soruyu burada cevapladım ).


Bu iyi bir tavsiye, ancak bunun uygulanamadığı durumlar da var, örn. bir formset için özel model formu.
Wtower

8
Birden fazla form ve daha sonra bunları aynı <form>unsurda birleştiren bir şablon gösterme yeteneğine sahip sınıf temelli bir görünümü yapmanın basit bir yolu ne olabilir ?
jozxyqk

1
Ama nasıl? Genellikle bir FormViewyalnızca kendisine form_classatanmış bir tek kişiye sahiptir .
erikbwork

@erikbwork Bu durum için FormView kullanmamalısınız. Sadece Alt Sınıflandırın TemplateViewve FormView ile aynı mantığı uygulayın, ancak birden çok formla.
moppag

10

Bu kod parçalarını kullanmayı deneyebilirsiniz:

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

Örnek Kullanım:

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

Görünüşe göre bu, bazı açık kontroller nedeniyle yöneticide kullanılamıyor:admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
WhyNotHugo

İle nasıl kullanabilirim UpdateView?
Pavel Shlepnev

4

erikbwork ve benim, ikimizin de sorunu, yalnızca bir modeli genel bir Sınıf Tabanlı Görünüme dahil edebiliyordu. Miao gibi benzer bir yaklaşım buldum ama daha modüler.

Tüm genel Sınıf Tabanlı Görünümleri kullanabilmeniz için bir Mixin yazdım. Modeli, alanları ve şimdi de child_model ve child_field'ı tanımlayın - ve ardından her iki modelin alanlarını Zach'in açıkladığı gibi bir etikete sarabilirsiniz.

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

Örnek Kullanım:

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

Veya ModelFormClass ile:

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

Bitti. Umarım bu birine yardımcı olur.


Bunda save_child_form.course_key = self.objectne var .course_key?
Adam Starrh

Bence course_key ilgili model, benim durumumda UserProfile.user'de olduğu gibi "user", yani bir geri referans, belki de bu alan adı yeniden kullanılabilir bir mixin olacaksa özelleştirilebilir olmalıdır. Ancak yine de alt formun aslında ilk verilerle doldurulmadığı başka bir sorun yaşıyorum, Kullanıcı'daki tüm alanlar önceden doldurulmuş ancak UserProfile için değil. Önce bunu düzeltmem gerekebilir.
robvdl

Alt formun doldurulmamasının nedeni, get_child_form yönteminde çağırması return self.child_form_class(**self.get_form_kwargs())ancak yanlış model örneğini almasıdır kwargs['instance'], örneğin alt model değil, örnek ana modeldir. Düzeltmek için kwarg'ları bir değişkene kaydetmeniz ve kwargs = self.get_form_kwargs()ardından kwargs['initial']çağırmadan önce doğru model örneğiyle güncellemeniz gerekir return self.child_form_class(**kwargs). Benim durumumda bu kwargs['instance'] = kwargs['instance'].profilemantıklıysa öyleydi .
robvdl

Ne yazık ki kaydettiğinizde, biri self.object'in henüz form_valid'de bulunmadığı, dolayısıyla bir AttributeError attığı ve başka bir yer örneği olmadığı durumda iki yerde çökecektir. Bu çözümün gönderilmeden önce tamamen test edilip edilmediğinden emin değilim, bu nedenle CombinedFormBase kullanarak diğer yanıta gitmek daha iyi olabilir.
robvdl

1
Nedir model_forms?
Granny Aching

2

Muhtemelen Satır içi form setlerine bir göz atmalısınız . Satır içi form kümeleri, modelleriniz bir yabancı anahtarla ilişkilendirildiğinde kullanılır.


1
Satır içi form kümeleri, bire çok ilişkiyle çalışmanız gerektiğinde kullanılır. Çalışan eklediğiniz bir şirket gibi. 2 tabloyu tek bir formda birleştirmeye çalışıyorum. Bu bire bir ilişkidir.
Jason Webb

Bir Inline form setinin kullanılması işe yarayacak, ancak muhtemelen idealden daha az olacaktır. Ayrıca, ilişkiyi sizin için yöneten bir Model oluşturabilir ve ardından tek bir form kullanabilirsiniz. Stackoverflow.com/questions/2770810/… adresinde önerilen 2 formlu tek bir sayfaya sahip olmak işe yarayabilir.
John Percival Hackworth

2

Benzer bir sorun için cevabımı buradan kontrol edebilirsiniz .

Kayıt ve kullanıcı profilinin tek bir formda nasıl birleştirileceğinden bahsediyor, ancak herhangi bir ModelForm kombinasyonuna genelleştirilebilir.


1

Ben kullanılan Django betterforms 'ın çok şekilli ve MultiModelForm projeme. Yine de kod geliştirilebilir. Örneğin, 3. + tarafından desteklenmeyen django.six'e bağlıdır, ancak bunların tümü kolayca düzeltilebilir.

Bu soru StackOverflow'da birkaç kez ortaya çıktı , bu yüzden bununla başa çıkmanın standart bir yolunu bulmanın zamanı geldiğini düşünüyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.