Django - CreateView, iç içe form kümesiyle formu kaydetmiyor


14

Django-Crispy-Forms mizanpaj özelliğini kullanarak, iç formları ana form ile kaydetmek için bir yaklaşımı uyarlamaya çalışıyorum ama kaydedemiyorum. Bu kod örneği projesi takip ediyorum ama veri kaydetmek için onaylanmış form kümesi alınamadı. Birisi hatamı gösterebilirse gerçekten minnettar olacağım. Ayrıca EmployeeForm için aynı görünümde üç satır eklemek gerekir. Django-Extra-Views'u denedim ama bu işi yapamadım. 5 gibi aynı görünüm için birden fazla satır içi eklemenizi tavsiye edersiniz. Tüm Ben oluşturmak için tek bir sayfa Employeeve onun satır içi gibi ulaşmak istiyorum Education, Experience, Others. Kod aşağıdadır:

modeller:

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)

Görünüm:

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

Formlar:

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')),
                )
            )

Örnek olarak Özel Düzen Nesnesi:

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>

Terminalde ve / veya başka hiçbir hata yok. Yardım çok takdir edilmektedir.


Alternatif bir çözüm formu da form seti işlemek için: schinckel.net/2019/05/23/form-and-formset
Matthew Schinckel

Yanıtlar:


0

Şu anda form kümenizi bilgisayarınızda doğru şekilde işlemiyorsunuz CreateView. form_validbu görünümde yalnızca üst form işlenir, form kümeleri değil. Yapmanız gereken postyöntemi geçersiz kılmaktır ve orada hem formu hem de ona bağlı olan form kümelerini doğrulamanız gerekir:

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)

Sonra şöyle değiştirin form_valid:

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

Şu anda kullandığınız yol get_context_data()doğru değil - bu yöntemi tamamen kaldırın. Yalnızca şablon oluşturmak için bağlam verilerini almak için kullanılmalıdır. Bunu form_valid()yönteminizden çağırmamalısınız . Bunun yerine, formetini post()yukarıda açıklandığı gibi yöntemden bu yönteme geçirmeniz gerekir .

Umarım bunu anlamanıza yardımcı olacak yukarıdaki örnek kodda birkaç ek yorum bıraktım.


Lütfen yanıtlamadan önce yerel olarak bir örnek oluşturun. Eserini denedim ama çalışmıyor.
Shazia Nusrat

1
@ShaziaNusrat üzgünüm, sizin için neyin işe yaramadığını denemek ve çözmek için zamanım yok, özellikle neyi denediğinizi ve neyin işe yaramadığını söylemiyorsanız ("Çalışmıyor" bir işe yaramayan şeyin yeterli açıklaması). Cevabımda, mevcut uygulamanızla neyi değiştirmeniz gerektiğini belirlemenize yardımcı olacak kadar yeterli olduğuna inanıyorum. Değilse, umarım başka biri size daha kapsamlı bir cevap verebilir.
solarissmoke

Test için kodda denedim ve problemlerle koştu. Bu yüzden alçakgönüllülükle yanınızda denemenizi rica ediyorum, böylece daha iyi rehberlik edebilirsiniz. Bana yardım etmek için biraz zaman ayırdığın için minnettarım. Ama çalışmıyor.
Shazia Nusrat

0

Belki paketi görmek istersiniz django-extra-views, görünümü sağlar CreateWithInlinesView, cadı Django-admin satır içi gibi iç içe satırlarla form oluşturmanıza olanak tanır.

Senin durumunda, böyle bir şey olurdu:

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>

Görünüm EmployeeCreateView, formları sizin için Django-admin'de olduğu gibi işleyecektir. Bu noktadan sonra formlara istediğiniz stili uygulayabilirsiniz.

Daha fazla bilgi için belgeleri ziyaret etmenizi tavsiye ederim

DÜZENLENDİ: Ekledimmanagement_form / kaldıracak js düğmeleri ekledim .


Bunu zaten denedim, ancak birden fazla satır için ekleme / silme düğmeleri kullanmama izin vermiyor. JS düğmeleriyle yalnızca bir satır içi desteği destekler. Bunu zaten denedim.
Shazia Nusrat

1
Onu destekler, management_formher biri için eklemeniz gerekirformset
John

0

Bir hata olduğunu söylediniz, ancak sorunuzda göstermiyorsunuz. Hata (ve tüm geri izleme) yazdığınız her şeyden daha önemlidir (forms.py ve views.py dışında olabilir)

Kasanız, kalıplar nedeniyle ve aynı CreateView'da birden çok form kullanması nedeniyle biraz daha karmaşıktır. İnternette çok (ya da pek iyi değil) örnek yok. Django kodunu satır içi kalıpların nasıl çalıştığını kazana kadar sorun yaşayacaksınız.

Doğrudan noktaya ok. Sorununuz, form kümelerinin ana formunuzla aynı örnekle başlatılmamış olmasıdır. Amin formunuz verileri veritabanına kaydettiğinde, form kümesindeki örnek değişmez ve sonunda yabancı anahtar olarak konulması için ana nesnenin kimliğine sahip olmazsınız. İnit sonrasında form özniteliğinin örnek niteliğini değiştirmek iyi bir fikir değildir.

Normal formlarda, is_valid değerinden sonra değiştirirseniz, öngörülemeyen sonuçlarınız olur. İnit komutundan hemen sonra bile örnek özniteliğini değiştiren form setleri için, form kümesindeki formların bir örnekle zaten başlatılmış olmasına ve sonradan değiştirilmesinin yardımcı olmayacaktır. İyi haber, Formset başlatıldıktan sonra örneğin niteliklerini değiştirebilmenizdir, çünkü form kümesi başlatıldıktan sonra tüm formların örnek nitelikleri aynı nesneyi gösterecektir.

İki seçeneğiniz var:

Form kümesi için örnek niteliğini ayarlamak yerine, yalnızca instance.pk değerini ayarlayın. (Bu sadece hiç yapmadım bir tahmin ama bence işe yarayacak. Sorun hack gibi görünecek). Tüm formları / form kümelerini bir seferde başlatacak bir form oluşturun. İs_valid () yöntemi çağrıldığında tüm kodlar doğrulanmalıdır. Save () yöntemi çağrıldığında tüm formlar kaydedilmelidir. Ardından, CreateView öğenizin form_class özniteliğini bu form sınıfına ayarlamanız gerekir. Tek zor kısım, ana formunuz başlatıldıktan sonra diğerlerini (formest) ilk formunuzun örneğiyle başlatmanız gerektiğidir. Ayrıca, şablonda bunlara erişebilmek için formları / form kümelerini formunuzun nitelikleri olarak ayarlamanız gerekir. Tüm ilgili nesnelerle bir nesne oluşturmam gerektiğinde ikinci yaklaşımı kullanıyorum.

is_valid () ile geçerliliği kontrol edilen bazı verilerle (bu durumda POST verileri) başlatıldığında, geçerli olduğunda save () ile kaydedilebilir. Form arabirimini korursunuz ve formunuzu doğru bir şekilde oluşturduysanız, bunu yalnızca oluşturmak için değil, ilgili nesnelerle birlikte nesneleri güncellemek için de kullanabilirsiniz ve görünümler çok basit olacaktır.

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.