Django - CreatView không lưu biểu mẫu với bộ định dạng lồng nhau


14

Tôi đang cố gắng điều chỉnh cách tiếp cận để lưu các biểu mẫu lồng nhau với hình thức chính bằng tính năng bố trí Django-Crispy-Forms nhưng tôi không thể lưu nó. Tôi đang theo dõi dự án ví dụ mã này nhưng không thể xác thực định dạng để lưu dữ liệu. Tôi sẽ thực sự biết ơn nếu ai đó có thể chỉ ra sai lầm của tôi. Tôi cũng cần thêm ba dòng trong cùng một góc nhìn cho EmployeeForm. Tôi đã thử Django-Extra-Views nhưng không thể thực hiện được. Sẽ đánh giá cao nếu bạn khuyên nên thêm nhiều hơn một dòng cho cùng một chế độ xem như khoảng 5. Tất cả tôi muốn đạt được rằng một trang duy nhất để tạo Employeevà nội tuyến của nó như thế nào Education, Experience, Others. Dưới đây là mã:

mô hình:

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)

Lượt xem:

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

Các hình thức:

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

Đối tượng bố trí tùy chỉnh theo ví dụ:

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>

Không có lỗi trong thiết bị đầu cuối và hoặc cách khác. Giúp đỡ được nhiều đánh giá cao.


Một giải pháp thay thế là cũng có biểu mẫu xử lý bộ định dạng: Tôi thực hiện bằng cách sử dụng bộ đệm ẩn cho bộ định dạng có liên quan trong schinckel.net/2019/05/23/form-and-formset
Matthew Schinckel

Câu trả lời:


0

Bạn hiện không xử lý bộ định dạng đúng trong CreateView. form_validtrong chế độ xem đó sẽ chỉ xử lý biểu mẫu chính, không phải biểu mẫu. Những gì bạn nên làm là ghi đè postphương thức và ở đó bạn cần xác thực cả biểu mẫu và bất kỳ biểu mẫu nào được đính kèm với nó:

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)

Sau đó, bạn sửa đổi form_validnhư vậy:

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

Theo cách bạn hiện đang sử dụng get_context_data()không chính xác - hãy xóa hoàn toàn phương pháp đó. Nó chỉ nên được sử dụng để tìm nạp dữ liệu ngữ cảnh để hiển thị một mẫu. Bạn không nên gọi nó từ form_valid()phương pháp của bạn . Thay vào đó, bạn cần truyền formset cho phương thức này từ post()phương thức như đã nêu ở trên.

Tôi đã để lại một vài nhận xét bổ sung trong mã mẫu ở trên, hy vọng sẽ giúp bạn tìm ra điều này.


Vui lòng tạo lại một ví dụ cục bộ trước khi bạn trả lời. Tôi đã thử tác phẩm của bạn nhưng không hoạt động.
Shazia Nusrat

1
@ShaziaNusrat xin lỗi, tôi không có thời gian để thử và tìm ra những gì không hiệu quả với bạn đặc biệt là nếu bạn không nói những gì bạn đã thử và những gì không hoạt động ("Nó không hoạt động" không phải là một mô tả đầy đủ về những gì không làm việc). Tôi tin rằng có đủ trong câu trả lời của tôi để giúp bạn xác định những gì bạn cần thay đổi với việc triển khai hiện tại của bạn. Nếu không, hãy hy vọng người khác sẽ có thể cung cấp cho bạn câu trả lời toàn diện hơn.
solarissmoke

Tôi đã thử nó trong mã để thử nghiệm và nó chạy với vấn đề. Đó là lý do tại sao tôi khiêm tốn yêu cầu bạn thử nó ở bên bạn để bạn có thể hướng dẫn tôi tốt hơn. Tôi rất biết ơn khi bạn đã dành thời gian để giúp tôi. Nhưng không hoạt động.
Shazia Nusrat

0

Có thể bạn muốn xem gói django-extra-views, cung cấp chế độ xemCreateWithInlinesView , phù thủy cho phép bạn tạo biểu mẫu với các đường thẳng lồng nhau như đường viền Django-admin.

Trong trường hợp của bạn, nó sẽ là một cái gì đó như thế:

lượt xem

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>

Quan điểm EmployeeCreateView sẽ xử lý các biểu mẫu cho bạn như trong Django-admin. Từ thời điểm này, bạn có thể áp dụng phong cách bạn muốn cho các hình thức.

Tôi khuyên bạn nên truy cập tài liệu để biết thêm thông tin

EDITED: Tôi đã thêm management_form và các nút js để thêm / xóa.


Tôi đã thử điều đó rồi nhưng điều đó sẽ không cho phép tôi có các nút thêm / xóa cho nhiều dòng. Nó chỉ hỗ trợ một dòng với các nút JS. Tôi đã thử nó rồi.
Shazia Nusrat

1
Nó hỗ trợ nó, bạn phải thêm management_formcho mỗiformset
John

0

Bạn nói rằng có một lỗi nhưng bạn không hiển thị nó trong câu hỏi của bạn. Lỗi (và toàn bộ truy nguyên) quan trọng hơn bất cứ điều gì bạn đã viết (ngoại trừ có thể là từ Forms.py và view.py)

Trường hợp của bạn phức tạp hơn một chút vì các biểu mẫu và sử dụng nhiều biểu mẫu trên cùng một Tạo. Không có nhiều ví dụ (hoặc không nhiều) tốt trên internet. Cho đến khi bạn đào mã django, các bộ định tuyến đang hoạt động như thế nào bạn sẽ gặp rắc rối.

Ok đi thẳng vào vấn đề. Vấn đề của bạn là các biểu mẫu không được khởi tạo với cùng thể hiện với biểu mẫu chính của bạn. Và khi biểu mẫu amin của bạn lưu dữ liệu vào cơ sở dữ liệu, thể hiện trong bộ định dạng không bị thay đổi và cuối cùng, bạn không có ID của đối tượng chính để được đặt làm khóa ngoại. Thay đổi thuộc tính thể hiện của thuộc tính biểu mẫu sau init không phải là ý hay.

Trong các hình thức bình thường nếu bạn chnage nó sau is_valid, bạn sẽ có kết quả không thể đoán trước. Đối với các biểu mẫu thay đổi thuộc tính cá thể ngay cả ngay sau khi init sẽ không hoạt động, vì các biểu mẫu trong bộ định dạng đã được khởi tạo với một số phiên bản và thay đổi nó sau sẽ không có ích. Tin vui là bạn có thể thay đổi các thuộc tính của thể hiện sau khi Formset được khởi tạo, bởi vì tất cả các thuộc tính thể hiện của biểu mẫu sẽ trỏ đến cùng một đối tượng sau khi formset được khởi tạo.

Bạn có hai lựa chọn:

Thay vì đặt thuộc tính cá thể nếu bộ định dạng, chỉ đặt instance.pk. (Đây chỉ là một phỏng đoán tôi chưa bao giờ làm điều đó nhưng tôi nghĩ nó nên hoạt động. Vấn đề là nó sẽ trông giống như hack). Tạo một biểu mẫu sẽ khởi tạo tất cả các biểu mẫu / biểu mẫu cùng một lúc. Khi phương thức is_valid () được gọi, tất cả các fomrs sẽ được xác nhận. Khi phương thức save () được gọi, tất cả các biểu mẫu phải được lưu. Sau đó, bạn cần đặt thuộc tính form_group của CreatView của bạn cho lớp biểu mẫu đó. Phần khó khăn duy nhất là sau khi biểu mẫu chính của bạn được khởi tạo, bạn cần khởi tạo các biểu mẫu khác (biểu mẫu) với thể hiện của biểu mẫu đầu tiên của bạn. Ngoài ra, bạn cần đặt biểu mẫu / biểu mẫu làm thuộc tính của biểu mẫu để có quyền truy cập vào biểu mẫu trong mẫu. Tôi đang sử dụng cách tiếp cận thứ hai khi tôi cần tạo một đối tượng với tất cả các đối tượng liên quan.

được khởi tạo với một số dữ liệu (trong trường hợp này là dữ liệu POST) được kiểm tra tính hợp lệ với is_valid () có thể được lưu với save () khi nó hợp lệ. Bạn giữ nguyên giao diện biểu mẫu và nếu bạn tạo biểu mẫu của mình một cách chính xác, bạn thậm chí có thể sử dụng nó không chỉ để tạo mà còn cập nhật các đối tượng cùng với các đối tượng liên quan của họ và các chế độ xem sẽ rất đơn giản.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.