Django - CreateView tidak menyimpan formulir dengan format bersarang

14

Saya mencoba untuk mengadaptasi pendekatan untuk menyimpan formet bersarang dengan bentuk utama menggunakan fitur tata letak Django-Crispy-Forms tapi saya tidak bisa menyimpannya. Saya mengikuti ini contoh proyek kode tapi tidak bisa mendapatkan formset divalidasi untuk menyimpan data. Saya akan sangat berterima kasih jika seseorang bisa menunjukkan kesalahan saya. Saya juga perlu menambahkan tiga inline dalam tampilan yang sama untuk EmployeeForm. Saya mencoba Django-Extra-Views tetapi tidak berhasil. Sangat menghargai jika Anda menyarankan untuk menambahkan lebih dari satu inline untuk tampilan yang sama seperti di sekitar 5. Yang ingin saya capai yaitu satu halaman untuk membuat Employeedan inline-nya suka Education, Experience, Others. Di bawah ini adalah kode:

model:

class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employees',
                                null=True, blank=True)
    about = models.TextField()
    street = models.CharField(max_length=200)
    city = models.CharField(max_length=200)
    country = models.CharField(max_length=200)
    cell_phone = models.PositiveIntegerField()
    landline = models.PositiveIntegerField()

    def __str__(self):
        return '{} {}'.format(self.id, self.user)

    def get_absolute_url(self):
        return reverse('bars:create', kwargs={'pk':self.pk})

class Education(models.Model):
    employee = models.ForeignKey('Employee', on_delete=models.CASCADE, related_name='education')
    course_title = models.CharField(max_length=100, null=True, blank=True)
    institute_name = models.CharField(max_length=200, null=True, blank=True)
    start_year = models.DateTimeField(null=True, blank=True)
    end_year = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return '{} {}'.format(self.employee, self.course_title)

Melihat:

class EmployeeCreateView(CreateView):
    model = Employee
    template_name = 'bars/crt.html'
    form_class = EmployeeForm
    success_url = None

    def get_context_data(self, **kwargs):
        data = super(EmployeeCreateView, self).get_context_data(**kwargs)
        if self.request.POST:
            data['education'] = EducationFormset(self.request.POST)
        else:
            data['education'] = EducationFormset()
        print('This is context data {}'.format(data))
        return data


    def form_valid(self, form):
        context = self.get_context_data()
        education = context['education']
        print('This is Education {}'.format(education))
        with transaction.atomic():
            form.instance.employee.user = self.request.user
            self.object = form.save()
            if education.is_valid():
                education.save(commit=False)
                education.instance = self.object
                education.save()

        return super(EmployeeCreateView, self).form_valid(form)

    def get_success_url(self):
        return reverse_lazy('bars:detail', kwargs={'pk':self.object.pk})

Formulir:

class EducationForm(forms.ModelForm):
    class Meta:
        model = Education
        exclude = ()
EducationFormset =inlineformset_factory(
    Employee, Education, form=EducationForm,
    fields=['course_title', 'institute_name'], extra=1,can_delete=True
    )

class EmployeeForm(forms.ModelForm):

    class Meta:
        model = Employee
        exclude = ('user', 'role')

    def __init__(self, *args, **kwargs):
        super(EmployeeForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = True
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-3 create-label'
        self.helper.field_class = 'col-md-9'
        self.helper.layout = Layout(
            Div(
                Field('about'),
                Field('street'),
                Field('city'),
                Field('cell_phone'),
                Field('landline'),
                Fieldset('Add Education',
                    Formset('education')),
                HTML("<br>"),
                ButtonHolder(Submit('submit', 'save')),
                )
            )

Objek Tata Letak Kustom seperti contoh:

from crispy_forms.layout import LayoutObject, TEMPLATE_PACK
from django.shortcuts import render
from django.template.loader import render_to_string

class Formset(LayoutObject):
    template = "bars/formset.html"

    def __init__(self, formset_name_in_context, template=None):
        self.formset_name_in_context = formset_name_in_context
        self.fields = []
        if template:
            self.template = template

    def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
        formset = context[self.formset_name_in_context]
        return render_to_string(self.template, {'formset': formset})

Formset.html:

{% load static %}
{% load crispy_forms_tags %}
{% load staticfiles %}

<table>
{{ formset.management_form|crispy }}

    {% for form in formset.forms %}
            <tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
                {% for field in form.visible_fields %}
                <td>
                    {# Include the hidden fields in the form #}
                    {% if forloop.first %}
                        {% for hidden in form.hidden_fields %}
                            {{ hidden }}
                        {% endfor %}
                    {% endif %}
                    {{ field.errors.as_ul }}
                    {{ field|as_crispy_field }}
                </td>
                {% endfor %}
            </tr>
    {% endfor %}

</table>
<br>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script src="{% static 'js/jquery.formset.js' %}">
</script>
<script type="text/javascript">
    $('.formset_row-{{ formset.prefix }}').formset({
        addText: 'add another',
        deleteText: 'remove',
        prefix: '{{ formset.prefix }}',
    });
</script>

Tidak ada kesalahan di terminal dan atau sebaliknya. Bantuan sangat dihargai.

Shazia Nusrat
sumber
Solusi alternatif adalah memiliki formulir yang menangani formset juga: Saya melakukannya menggunakan cached_property untuk formset terkait di schinckel.net/2019/05/23/form-and-formset
Matthew Schinckel

Jawaban:

0

Anda saat ini tidak memproses formset dengan benar di CreateView. form_validdalam pandangan itu hanya akan menangani form induk, bukan formet. Apa yang harus Anda lakukan adalah mengganti postmetode, dan di sana Anda perlu memvalidasi baik formulir dan setiap formset yang dilampirkan:

def post(self, request, *args, **kwargs):
    form = self.get_form()
    # Add as many formsets here as you want
    education_formset = EducationFormset(request.POST)
    # Now validate both the form and any formsets
    if form.is_valid() and education_formset.is_valid():
        # Note - we are passing the education_formset to form_valid. If you had more formsets
        # you would pass these as well.
        return self.form_valid(form, education_formset)
    else:
        return self.form_invalid(form)

Kemudian Anda memodifikasi form_validseperti ini:

def form_valid(self, form, education_formset):
    with transaction.atomic():
        form.instance.employee.user = self.request.user
        self.object = form.save()
        # Now we process the education formset
        educations = education_formset.save(commit=False)
        for education in educations:
            education.instance = self.object
            education.save()
        # If you had more formsets, you would accept additional arguments and
        # process them as with the one above.
    # Don't call the super() method here - you will end up saving the form twice. Instead handle the redirect yourself.
    return HttpResponseRedirect(self.get_success_url())

Cara yang Anda gunakan saat get_context_data()ini tidak benar - hapus metode itu sepenuhnya. Seharusnya hanya digunakan untuk mengambil data konteks untuk membuat template. Anda tidak boleh menyebutnya dari form_valid()metode Anda . Alih-alih, Anda perlu meneruskan formset ke metode ini dari post()metode seperti diuraikan di atas.

Saya telah meninggalkan beberapa komentar tambahan dalam kode contoh di atas yang diharapkan akan membantu Anda mengetahui hal ini.

solarissmoke
sumber
Harap buat kembali contoh secara lokal sebelum Anda menjawab. Saya sudah mencoba bagian Anda tetapi tidak berhasil.
Shazia Nusrat
1
@ShaziaNusrat maaf, saya tidak punya waktu untuk mencoba dan mencari tahu apa yang tidak berhasil untuk Anda terutama jika Anda tidak mengatakan apa yang telah Anda coba dan yang tidak berhasil ("Ini tidak berfungsi" bukan deskripsi yang memadai tentang apa yang tidak berhasil). Saya percaya ada cukup dalam jawaban saya untuk membantu Anda mengidentifikasi apa yang perlu Anda ubah dengan implementasi Anda saat ini. Jika tidak, maka mari kita berharap orang lain akan dapat memberi Anda jawaban yang lebih komprehensif.
solarissmoke
Saya mencobanya dalam kode untuk pengujian dan itu berjalan dengan masalah. Itu sebabnya saya dengan rendah hati meminta Anda untuk mencobanya di sisi Anda secara lokal sehingga Anda dapat membimbing saya dengan lebih baik. Saya bersyukur karena Anda meluangkan waktu untuk membantu saya. Tapi tidak berhasil.
Shazia Nusrat
0

Mungkin Anda ingin melihat paket django-extra-views, tampilan menyediakan CreateWithInlinesView, penyihir memungkinkan Anda untuk membuat formulir dengan inline bersarang seperti Django-admin inlines.

Dalam kasus Anda, itu akan menjadi sesuatu seperti itu:

views.py

class EducationInline(InlineFormSetFactory):
    model = Education
    fields = ['course_title', 'institute_name']


class EmployeeCreateView(CreateWithInlinesView):
    model = Employee
    inlines = [EducationInline,]
    fields = ['about', 'street', 'city', 'cell_phone', 'landline']
    template_name = 'bars/crt.html'

crt.html

<form method="post">
  ...
  {{ form }}
  <table>
  {% for formset in inlines %}
    {{ formset.management_form }}
      {% for inline_form in formset %}
        <tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
          {{ inline_form }}
        </tr>
      {% endfor %}
  {% endfor %}
  </table>
  ...
  <input type="submit" value="Submit" />
</form>

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script src="{% static 'js/jquery.formset.js' %}">
</script>
<script type="text/javascript">
    {% for formset in inlines %}
      $('.formset_row-{{ formset.prefix }}').formset({
          addText: 'add another',
          deleteText: 'remove',
          prefix: '{{ formset.prefix }}',
      });
    {% endfor %}
</script>

Tampilan EmployeeCreateViewakan memproses formulir untuk Anda seperti di Django-admin. Dari titik ini Anda bisa menerapkan gaya yang Anda inginkan ke formulir.

Saya sarankan Anda mengunjungi dokumentasi untuk informasi lebih lanjut

Diedit: Saya menambahkan management_form dan tombol js untuk menambah / menghapus.

John
sumber
Saya sudah mencobanya tetapi itu tidak akan membiarkan saya menambahkan / menghapus tombol untuk beberapa inline. Ini hanya mendukung satu inline dengan tombol JS. Saya sudah mencobanya.
Shazia Nusrat
1
Ini mendukungnya, Anda harus menambahkan management_formuntuk masingformset
John
0

Anda mengatakan bahwa ada kesalahan tetapi Anda tidak menunjukkannya dalam pertanyaan Anda. Kesalahan (dan seluruh traceback) lebih penting daripada apa pun yang Anda tulis (kecuali mungkin dari forms.py dan views.py)

Kasing Anda sedikit lebih rumit karena format dan menggunakan beberapa formulir pada CreateView yang sama. Tidak banyak (atau tidak banyak contoh) di internet. Sampai Anda menggali dalam kode Django bagaimana formline inline bekerja Anda akan mengalami masalah.

Ok langsung ke intinya. Masalah Anda adalah bahwa formet tidak diinisialisasi dengan instance yang sama dengan form utama Anda. Dan ketika formulir amin Anda menyimpan data ke database, instance dalam formset tidak berubah dan pada akhirnya Anda tidak memiliki ID objek utama untuk dimasukkan sebagai kunci asing. Mengubah atribut instance dari atribut form setelah init bukanlah ide yang baik.

Dalam bentuk normal jika Anda chnage setelah is_valid Anda akan memiliki hasil yang tidak terduga. Untuk formet yang mengubah atribut instance bahkan langsung setelah init tidak akan melakukan apa-apa, karena form dalam formset sudah diinisialisasi dengan beberapa instance, dan mengubahnya setelah itu tidak akan membantu. Berita baiknya adalah Anda dapat mengubah atribut instance setelah Formset diinisialisasi, karena semua atribut instance form akan menunjuk ke objek yang sama setelah formset diinisialisasi.

Anda memiliki dua opsi:

Alih-alih mengatur atribut instance jika formset, atur hanya instance.pk. (Ini hanya dugaan saya belum pernah melakukannya tetapi saya pikir itu harus berhasil. Masalahnya adalah itu akan terlihat seperti hack). Buat formulir yang akan menginisialisasi semua formulir / formulir sekaligus. Ketika metode is_valid () dipanggil semua fomrs harus divalidasi. Ketika metode save () dipanggil, semua formulir harus disimpan. Maka Anda perlu mengatur atribut form_class dari CreateView Anda ke kelas bentuk itu. Satu-satunya bagian yang sulit adalah setelah form utama Anda diinisialisasi, Anda perlu menginisialisasi yang lain (formests) dengan instance dari form pertama Anda. Anda juga perlu mengatur form / formet sebagai atribut dari form Anda untuk memiliki akses ke mereka di Templat. Saya menggunakan pendekatan kedua ketika saya harus membuat objek dengan semua objek terkait itu.

diinisialisasi dengan beberapa data (dalam hal ini data POST) diperiksa validitasnya dengan is_valid () dapat disimpan dengan save () ketika valid. Anda mempertahankan antarmuka bentuk dan jika Anda membuat formulir dengan benar, Anda bahkan dapat menggunakannya tidak hanya untuk membuat tetapi untuk memperbarui objek bersama-sama dengan objek terkait dan tampilan akan sangat sederhana.

Alexis Rouxel
sumber