Tự động thêm một biểu mẫu vào tập hợp Django với Ajax


260

Tôi muốn tự động thêm các biểu mẫu mới vào bộ định dạng Django bằng Ajax, để khi người dùng nhấp vào nút "thêm", nó sẽ chạy JavaScript để thêm một biểu mẫu mới (là một phần của bộ định dạng) vào trang.


Tôi chỉ đoán trường hợp sử dụng của bạn ở đây, có phải là tính năng "Đính kèm tệp khác" trong gmail, nơi người dùng được cung cấp trường tải lên tệp và các trường mới được thêm vào DOM khi người dùng nhấp vào vào nút "Đính kèm tập tin khác"?
Prairiedogg

Đây là điều mà tôi sẽ sớm thực hiện, vì vậy tôi cũng sẽ quan tâm đến bất kỳ câu trả lời nào.
Van Gale

2
Câu hỏi này hơi mờ, nó đề cập đến "Ajax" trong tiêu đề, mô tả và thẻ. Tuy nhiên, không có câu trả lời nào sử dụng Ajax, nó vẫn yêu cầu phải gửi biểu mẫu.
Antoine Pinsard

Câu trả lời:


219

Đây là cách tôi làm, sử dụng jQuery :

Mẫu của tôi:

<h3>My Services</h3>
{{ serviceFormset.management_form }}
{% for form in serviceFormset.forms %}
    <div class='table'>
    <table class='no_error'>
        {{ form.as_table }}
    </table>
    </div>
{% endfor %}
<input type="button" value="Add More" id="add_more">
<script>
    $('#add_more').click(function() {
        cloneMore('div.table:last', 'service');
    });
</script>

Trong tệp javascript:

function cloneMore(selector, type) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;
        $(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}

Những gì nó làm:

cloneMorechấp nhận selectorlàm đối số đầu tiên và định typedạng là đối số thứ hai. Những gì selectornên làm là vượt qua nó những gì nó nên nhân đôi. Trong trường hợp này, tôi chuyển nó div.table:lastđể jQuery tìm bảng cuối cùng với một lớp table. Một :lastphần của nó rất quan trọng vì selectorcũng được sử dụng để xác định hình thức mới sẽ được chèn vào sau. Nhiều khả năng bạn muốn nó ở phần còn lại của các biểu mẫu. Đối typesố là để chúng ta có thể cập nhật management_formtrường, đáng chú ý TOTAL_FORMS, cũng như các trường mẫu thực tế. Nếu bạn có một bộ định dạng đầy đủ, giả sử, Clientcác mô hình, các trường quản lý sẽ có ID của id_clients-TOTAL_FORMSid_clients-INITIAL_FORMS, trong khi các trường của biểu mẫu sẽ có định dạng id_clients-N-fieldnamevớiNlà số mẫu, bắt đầu bằng 0. Vì vậy, với những typelập luận cloneMorengoại hình chức năng có bao nhiêu hình thức hiện có, và đi qua tất cả các đầu vào và nhãn bên trong hình thức mới thay thế tất cả các tên trường / id từ một cái gì đó như id_clients-(N)-nameđể id_clients-(N+1)-namevà vân vân. Sau khi hoàn thành, nó cập nhật TOTAL_FORMStrường để phản ánh biểu mẫu mới và thêm nó vào cuối tập hợp.

Chức năng này đặc biệt hữu ích với tôi vì cách thiết lập nó cho phép tôi sử dụng nó trong suốt ứng dụng khi tôi muốn cung cấp nhiều biểu mẫu hơn trong một biểu mẫu và không khiến tôi phải có một biểu mẫu "mẫu" ẩn để sao chép miễn là tôi chuyển cho nó tên tập tin định dạng và định dạng mà các biểu mẫu được đặt ra. Hy vọng nó giúp.


Trong IE, một bản sao từ một phần tử được sao chép được biểu thị là <không xác định> khi chọn trong JS, tại sao?
panchicore

Tôi thấy rằng trong Django 1.1, bạn sẽ cần gán giá trị cho prefixthành viên của Đối tượng định dạng. Điều này sẽ có cùng giá trị như typeđối số cho cloneMorehàm.
Derek Reynold

3
Tôi đã sửa đổi điều này để lấy bộ chọn mà không có: cuối cùng và được sử dụng var Total = $ (bộ chọn) .length; để có được tổng số của tôi vì việc làm mới trang sẽ xóa các biểu mẫu của tôi nhưng để lại mức tăng TOTAL dẫn đến việc lưu sai số. Sau đó tôi đã thêm: cuối cùng cho bộ chọn khi cần thiết. Cảm ơn vì điều này.
Greg

2
Tôi đã thấy rằng điều này bằng cách sử dụng $ (this) .attr ({'name': name, 'id': id}). Val (''). RemoveAttr ('đã kiểm tra'); Để xóa đầu vào sẽ làm rối các hộp kiểm. Đặt val ('') cung cấp cho các hộp kiểm một thuộc tính giá trị trống. Và vì các hộp kiểm không sử dụng thuộc tính giá trị nên điều này sẽ không bao giờ được cập nhật - bất kể bạn nhấp vào nó bao nhiêu lần. Nhưng có vẻ như giá trị đó có mức độ ưu tiên cao hơn so với các hộp kiểm "được kiểm tra". Điều đó có nghĩa là bạn sẽ luôn đăng các hộp kiểm không được kiểm tra.
niklasdstrom

xin vui lòng paolo bạn có thể kiểm tra stackoverflow.com/questions/62252867/
nghệ thuật

109

Phiên bản đơn giản hóa câu trả lời của Paolo sử dụng empty_formlàm mẫu.

<h3>My Services</h3>
{{ serviceFormset.management_form }}
<div id="form_set">
    {% for form in serviceFormset.forms %}
        <table class='no_error'>
            {{ form.as_table }}
        </table>
    {% endfor %}
</div>
<input type="button" value="Add More" id="add_more">
<div id="empty_form" style="display:none">
    <table class='no_error'>
        {{ serviceFormset.empty_form.as_table }}
    </table>
</div>
<script>
    $('#add_more').click(function() {
        var form_idx = $('#id_form-TOTAL_FORMS').val();
        $('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
        $('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
    });
</script>

Làm thế nào tôi có thể đối phó với điều này trong quan điểm? Khi tôi sử dụng, CompetitorFormSet = modelformset_factory(ProjectCompetitor, formset=CompetitorFormSets) ctx['competitor_form_set'] = CompetitorFormSet(request.POST)tôi chỉ nhận được một hình thức, trong phương thức sạch. bạn có thể giải thích làm thế nào để giải quyết điều này trong quan điểm?
AJ

Rực rỡ - cảm ơn bạn. Sử dụng tuyệt vời các trình trợ giúp Django có sẵn (như empty_form), mà tôi đánh giá cao.
BigglesZX

@BigglesZX - Tôi đã điều chỉnh giải pháp và các hàng biểu mẫu trống mới đang được tạo. Tuy nhiên, các hộp chọn đang tạo danh sách các lựa chọn FK (khả dụng), thay vì thả xuống được tạo ra cho bộ biểu mẫu ban đầu. Có bất kỳ vấn đề về bản chất này đã được báo cáo?
dùng12379095

@Dave bạn có thể cập nhật câu trả lời cho các phiên bản sau tức là 3.x không? nó đơn giản và rõ ràng nhưng nó không hiệu quả với tôi
Poula Adel

1
@PoulaAdel Điều gì không hiệu quả? Tôi mới thử cái này trên Django 3.0.5 và nó vẫn hoạt động với tôi. Đáng ngạc nhiên sau 8 năm, nhưng tôi đoán Django và jQuery có khả năng tương thích ngược tốt với mã cũ.
Dave


18

Gợi ý của Paolo hoạt động rất đẹp với một cảnh báo - các nút quay lại / chuyển tiếp của trình duyệt.

Các phần tử động được tạo bằng tập lệnh của Paolo sẽ không được hiển thị nếu người dùng quay lại bộ định dạng bằng nút quay lại / chuyển tiếp. Một vấn đề có thể là một phá vỡ thỏa thuận cho một số.

Thí dụ:

1) Người dùng thêm hai biểu mẫu mới vào bộ định dạng bằng nút "thêm"

2) Người dùng điền vào biểu mẫu và gửi biểu mẫu

3) Người dùng nhấp vào nút quay lại trong trình duyệt

4) Formset hiện được giảm xuống dạng ban đầu, tất cả các dạng được thêm động đều không có

Đây hoàn toàn không phải là khiếm khuyết với kịch bản của Paolo; nhưng thực tế cuộc sống với thao tác dom và bộ nhớ cache của trình duyệt.

Tôi cho rằng người ta có thể lưu trữ các giá trị của biểu mẫu trong phiên và có một số phép thuật ajax khi bộ định dạng tải để tạo lại các phần tử và tải lại các giá trị từ phiên; nhưng tùy thuộc vào mức độ hậu môn mà bạn muốn về cùng một người dùng và nhiều trường hợp của biểu mẫu này có thể trở nên rất phức tạp.

Bất cứ ai có một đề nghị tốt để đối phó với điều này?

Cảm ơn!


2
Nếu bạn chuyển hướng sau khi gửi thành công, nút quay lại không phải là vấn đề. Nếu bạn điền các biểu mẫu từ DB vào lần truy cập tiếp theo, tất cả các biểu mẫu sẽ xuất hiện ban đầu. Nếu bạn không thành công các biểu mẫu do đầu vào không hợp lệ, tất cả chúng sẽ ở đó trên màn hình lại có lỗi. Trừ khi tôi không hiểu các tuyên bố của bạn .... Chuyển hướng gửi bài đăng đó thực sự quan trọng trong một ứng dụng hoạt động tốt, một điều mà rất nhiều lập trình viên không nhận được dựa trên số lượng ứng dụng hoạt động kém mà tôi chạy trên web.
thuyền viên

bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs


11

Mô phỏng và bắt chước:

  • Tạo một bộ định dạng tương ứng với tình huống trước khi nhấp vào nút "thêm".
  • Tải trang, xem nguồn và ghi chú tất cả các <input>trường.
  • Sửa đổi formset để tương ứng với tình huống sau khi nhấp vào nút "thêm" (thay đổi số lượng trường bổ sung).
  • Tải trang, xem nguồn và ghi chú về cách các <input>trường thay đổi.
  • Tạo một số JavaScript sửa đổi DOM theo cách phù hợp để di chuyển nó từ trạng thái trước sang trạng thái sau trạng thái .
  • Đính kèm JavaScript đó vào nút "thêm".

Mặc dù tôi biết các bộ định dạng sử dụng các <input>trường ẩn đặc biệt và biết khoảng kịch bản phải làm gì, tôi không nhớ các chi tiết ngoài đỉnh đầu. Những gì tôi mô tả ở trên là những gì tôi sẽ làm trong tình huống của bạn.


bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều stackoverflow.com/questions/62285767/ nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs

6

Có một plugin jquery cho cái này , tôi đã sử dụng nó với inline_form được thiết lập trong Django 1.3 và nó hoạt động hoàn hảo, bao gồm cả tiền xử lý, thêm biểu mẫu phía máy khách, xóa và nhiều inline_formsets.


Trong khi bài viết blog được liên kết vẫn tồn tại, các liên kết tải xuống ở đó bị hỏng. Rõ ràng, plugin được tạo bởi @ elo80ka, người có câu trả lời chỉ ra phiên bản (sơ bộ?) Của tập lệnh.
lfurini

bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs

4

Một tùy chọn sẽ là tạo một bộ định dạng với mọi hình thức có thể, nhưng ban đầu đặt các biểu mẫu không yêu cầu thành ẩn - tức là , display: none;. Khi cần hiển thị biểu mẫu, hãy đặt màn hình css thànhblock hoặc bất cứ điều gì phù hợp.

Không biết thêm chi tiết về "Ajax" của bạn đang làm gì, thật khó để đưa ra phản hồi chi tiết hơn.


4

Một phiên bản cloneMore khác, cho phép vệ sinh chọn lọc các trường. Sử dụng nó khi bạn cần ngăn một số trường bị xóa.

$('table tr.add-row a').click(function() {
    toSanitize = new Array('id', 'product', 'price', 'type', 'valid_from', 'valid_until');
    cloneMore('div.formtable table tr.form-row:last', 'form', toSanitize);
});

function cloneMore(selector, type, sanitize) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var namePure = $(this).attr('name').replace(type + '-' + (total-1) + '-', '');
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;
        $(this).attr({'name': name, 'id': id}).removeAttr('checked');

        if ($.inArray(namePure, sanitize) != -1) {
            $(this).val('');
        }

    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}

bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs

2

Có một vấn đề nhỏ với chức năng cloneMore. Vì nó cũng làm sạch giá trị của các trường ẩn được tạo tự động django, nó khiến django phàn nàn nếu bạn cố lưu một bộ định dạng có nhiều hơn một dạng trống.

Đây là một sửa chữa:

function cloneMore(selector, type) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;

        if ($(this).attr('type') != 'hidden') {
            $(this).val('');
        }
        $(this).attr({'name': name, 'id': id}).removeAttr('checked');
    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}

bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs

2

Tôi nghĩ rằng đây là một giải pháp tốt hơn nhiều.

Làm thế nào bạn có thể tạo một bộ định dạng động trong Django?

Có phải mọi thứ nhân bản không:

  • Thêm biểu mẫu khi không có biểu mẫu ban đầu tồn tại
  • Xử lý javascript ở dạng tốt hơn, ví dụ django-ckeditor
  • Giữ dữ liệu ban đầu

bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs

2

Đối với các lập trình viên ngoài kia đang săn lùng tài nguyên để hiểu các giải pháp trên tốt hơn một chút:

Hình thức động Django

Sau khi đọc liên kết trên, tài liệu Django và các giải pháp trước đó sẽ có ý nghĩa hơn rất nhiều.

Tài liệu định dạng Django

Như một bản tóm tắt nhanh chóng về những gì tôi đã bị nhầm lẫn bởi: Biểu mẫu quản lý chứa tổng quan về các biểu mẫu bên trong. Bạn phải giữ thông tin đó chính xác để Django nhận thức được các biểu mẫu bạn thêm. (Cộng đồng, xin vui lòng cho tôi đề xuất nếu một số từ ngữ của tôi bị tắt ở đây. Tôi mới biết về Django.)


1

@Paolo Bergantino

để sao chép tất cả các trình xử lý đính kèm chỉ cần sửa đổi dòng

var newElement = $(selector).clone();

cho

var newElement = $(selector).clone(true);

để ngăn chặn vấn đề này.


bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs

1

Yea tôi cũng khuyên bạn chỉ nên hiển thị chúng trong html nếu bạn có số lượng mục nhập hữu hạn. (Nếu bạn không phải sử dụng phương pháp khác).

Bạn có thể ẩn chúng như thế này:

{% for form in spokenLanguageFormset %}
    <fieldset class="languages-{{forloop.counter0 }} {% if spokenLanguageFormset.initial_forms|length < forloop.counter and forloop.counter != 1 %}hidden-form{% endif %}">

Sau đó, js thực sự đơn giản:

addItem: function(e){
    e.preventDefault();
    var maxForms = parseInt($(this).closest("fieldset").find("[name*='MAX_NUM_FORMS']").val(), 10);
    var initialForms = parseInt($(this).closest("fieldset").find("[name*='INITIAL_FORMS']").val(), 10);
    // check if we can add
    if (initialForms < maxForms) {
        $(this).closest("fieldset").find("fieldset:hidden").first().show();
        if ($(this).closest("fieldset").find("fieldset:visible").length == maxForms ){
            // here I'm just hiding my 'add' link
            $(this).closest(".control-group").hide();
        };
    };
}

bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs

1

Bởi vì tất cả các câu trả lời ở trên đều sử dụng jQuery và làm cho một số thứ hơi phức tạp, tôi đã viết đoạn script sau:

function $(selector, element) {
    if (!element) {
        element = document
    }
    return element.querySelector(selector)
}

function $$(selector, element) {
    if (!element) {
        element = document
    }
    return element.querySelectorAll(selector)
}

function hasReachedMaxNum(type, form) {
    var total = parseInt(form.elements[type + "-TOTAL_FORMS"].value);
    var max = parseInt(form.elements[type + "-MAX_NUM_FORMS"].value);
    return total >= max
}

function cloneMore(element, type, form) {
    var totalElement = form.elements[type + "-TOTAL_FORMS"];
    total = parseInt(totalElement.value);
    newElement = element.cloneNode(true);
    for (var input of $$("input", newElement)) {
        input.name = input.name.replace("-" + (total - 1) + "-", "-" + total + "-");
        input.value = null
    }
    total++;
    element.parentNode.insertBefore(newElement, element.nextSibling);
    totalElement.value = total;
    return newElement
}
var addChoiceButton = $("#add-choice");
addChoiceButton.onclick = function() {
    var choices = $("#choices");
    var createForm = $("#create");
    cloneMore(choices.lastElementChild, "choice_set", createForm);
    if (hasReachedMaxNum("choice_set", createForm)) {
        this.disabled = true
    }
};

Trước tiên, bạn nên đặt auto_id thành false và do đó vô hiệu hóa sao chép id và tên. Bởi vì tên đầu vào phải là duy nhất ở dạng đó, tất cả các nhận dạng được thực hiện với chúng chứ không phải với id. Bạn cũng phải thay thế form, typevà container của formset. (Trong ví dụ trên choices)


bạn có thể giúp tôi stackoverflow.com/questions/62285767/ , tôi đã thử rất nhiều nhưng không nhận được câu trả lời! tôi đánh giá cao bạn
art_cs
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.