Ajax를 사용하여 Django 폼셋에 폼을 동적으로 추가


260

Ajax를 사용하여 Django 폼셋에 새 폼을 자동으로 추가하고 싶습니다. 따라서 사용자가 "add"버튼을 클릭하면 새로운 폼 (폼셋의 일부)을 페이지에 추가하는 JavaScript가 실행됩니다.


유스 케이스에서 추측하는 것은 gmail의 "다른 파일 첨부"기능과 같은 것입니다. 사용자에게 파일 업로드 필드가 표시되고 사용자가 클릭하면 즉시 새 필드가 DOM에 추가됩니다 "다른 파일 첨부"플러스 버튼에?
prairiedogg

이것은 곧 진행될 작업이므로 답변에도 관심이 있습니다.
Van Gale

2
이 질문은 약간 모호합니다. 제목, 설명 및 태그에 "Ajax"가 언급되어 있습니다. 그러나 Ajax를 사용하는 답변은 없으며 양식을 제출해야합니다.
Antoine Pinsard

답변:


219

이것이 jQuery를 사용하여 수행하는 방법입니다 .

내 템플릿 :

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

자바 스크립트 파일에서 :

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

그것이하는 일 :

cloneMore받아들이는 selector첫 번째 인수로하고, type제 2 하나의 formset의. 무엇을 selector해야하는 것은 중복해야하는지 전달할 수 있습니다. 이 경우 div.table:lastjQuery가 클래스가있는 마지막 테이블을 찾도록 전달합니다 table. :last이 있기 때문에 그 부분은 중요하다 selector또한 새로운 형태 다음에 삽입 할 것을 결정하는 데 사용됩니다. 나머지 양식의 끝에 그것을 원할 것입니다. type우리는 업데이트 할 수 있도록 인수는 management_form특히 필드 TOTAL_FORMS뿐만 아니라 실제 양식 필드를. 당신은, 말하자면, 가득 해당 formset있는 경우 Client모델을 관리 필드의 ID를해야합니다 id_clients-TOTAL_FORMSid_clients-INITIAL_FORMS양식 필드의 형식이됩니다 반면, id_clients-N-fieldnameN로 시작하는 양식 번호입니다 0. 그래서 함께 type인수로 사용 cloneMore하는 방법을 여러 가지 형태의 기능 모습이 현재와 같은에서 모든 필드 이름 / ID를 대체하는 새로운 형태의 내부의 모든 입력 및 레이블을 통과 id_clients-(N)-name하는 id_clients-(N+1)-name등등. 완료된 후에는 TOTAL_FORMS필드를 업데이트하여 새 양식을 반영하고 세트 끝에 추가합니다.

이 기능은 설정 방식에 따라 양식 세트에서 더 많은 양식을 제공하려고 할 때 앱 전체에서 사용할 수 있으며 복제하기 위해 숨겨진 "템플릿"양식을 가질 필요가 없기 때문에 특히 유용합니다. 내가 그것을 전달하는 한 양식 이름과 양식이 배치되는 형식. 도움이 되길 바랍니다.


IE에서 복제 된 요소의 복제본은 JS에서 선택할 때 <정의되지 않음>으로 표시됩니다. 왜 그렇습니까?
panchicore

Django 1.1 prefix에서 Formset Object 의 멤버에 값을 할당해야한다는 것을 알았습니다 . 이것은 함수 의 type인수 와 같은 값이어야합니다 cloneMore.
Derek Reynolds

3
나는 이것을 last없이 사용하도록 선택자를 수정했다 : var total = $ (selector) .length; 페이지를 새로 고치면 양식 세트가 제거되지만 TOTAL 증가는 잘못된 수로 저장되므로 총계를 얻으려면. 그런 다음 필요에 따라 선택기에 : last를 추가했습니다. 감사합니다.
그렉

2
$ (this) .attr ({ 'name': name, 'id': id}). val ( ''). removeAttr ( 'checked'); 입력을 지우려면 확인란을 엉망으로 만듭니다. val ( '')을 설정하면 확인란에 빈 값 속성이 표시됩니다. 그리고 확인란은 value 속성을 사용하지 않으므로 클릭 횟수에 관계없이 업데이트되지 않습니다. 그러나 값은 확인란으로 인한 "확인 된"보다 우선 순위가 높은 것 같습니다. 이는 항상 체크되지 않은 확인란을 게시한다는 의미입니다.
niklasdstrom

당신은 내 문제를 확인할 수 있습니다 파올로하시기 바랍니다 stackoverflow.com/questions/62252867/...을
art_cs

109

empty_form템플릿으로 사용하는 Paolo의 답변 단순화 버전 .

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

이걸 어떻게 볼 수 있습니까? 내가 사용할 때 CompetitorFormSet = modelformset_factory(ProjectCompetitor, formset=CompetitorFormSets) ctx['competitor_form_set'] = CompetitorFormSet(request.POST)깨끗한 방법으로 한 가지 양식 만 가져옵니다. 이 문제를 처리하는 방법을 설명해 주시겠습니까?
AJ

훌륭합니다 – 감사합니다. 사용 가능한 Django 도우미 (예 :)를 empty_form잘 활용합니다.
BigglesZX 2016 년

@BigglesZX-솔루션을 조정했으며 빈 양식의 새로운 행이 생성됩니다. 그러나 선택 상자는 원래 양식 세트에 대해 생성되는 드롭 다운 대신 FK (사용 가능한) 선택 목록을 생성합니다. 이러한 성격의 문제가보고 되었습니까?
user12379095

@Dave 3.x 이후 버전에 대한 답변을 업데이트 할 수 있습니까? 간단하고 명확하지만 나를 위해 작동하지 않습니다
Poula Adel

1
@PoulaAdel 작동하지 않는 것은 무엇입니까? 방금 Django 3.0.5에서 이것을 시도했지만 여전히 효과가 있습니다. 8 년 후에 놀랍지 만 장고와 jQuery는 이전 코드와의 역 호환성이 뛰어납니다.
데이브


18

Paolo의 제안은 브라우저의 뒤로 / 앞으로 버튼이라는 하나의 경고와 함께 아름답게 작동합니다.

사용자가 뒤로 / 앞으로 단추를 사용하여 양식 세트로 돌아 오면 Paolo의 스크립트로 작성된 동적 요소가 렌더링되지 않습니다. 일부에게는 문제가 될 수있는 문제입니다.

예:

1) 사용자는 "추가 항목"버튼을 사용하여 두 개의 새 양식을 양식 세트에 추가합니다.

2) 사용자가 양식을 채우고 양식을 제출합니다.

3) 사용자가 브라우저에서 뒤로 버튼을 클릭

4) 이제 양식 세트가 원래 양식으로 축소되고 동적으로 추가 된 모든 양식이 없습니다

이것은 Paolo의 스크립트에 전혀 결함이 아닙니다. 그러나 DOM 조작과 브라우저 캐시의 삶의 사실.

양식에 값을 세션에 저장할 수 있고 양식 세트가 요소를 다시 작성하고 세션에서 값을 다시로드하기 위해로드 할 때 약간의 마법을 가질 수 있다고 가정합니다. 그러나 동일한 사용자와 양식의 여러 인스턴스에 대해 항문이 얼마나 필요한지에 따라 이것은 매우 복잡해질 수 있습니다.

누구든지 이것을 다루는 데 좋은 제안이 있습니까?

감사!


2
제출이 성공한 후 리디렉션하면 뒤로 버튼은 문제가되지 않습니다. 다음 방문시 DB에서 양식을 작성하면 모든 양식이 처음에 나타납니다. 유효하지 않은 입력으로 인해 양식에 실패하면 모든 오류가 다시 표시됩니다. 내가 당신의 진술을 이해하지 않는 한 .... 게시물 제출 리디렉션은 제대로 작동하는 응용 프로그램에서 실제로 중요합니다. 많은 코더가 웹에서 실행되는 제대로 작동하지 않는 응용 프로그램의 수를 기반으로하지 않습니다.
보트 코더

당신이 나를 도울 수 stackoverflow.com/questions/62285767/... , 내가 작정 시도했지만 답변을 얻을 didnt한다! 감사합니다
art_cs


11

시뮬레이션 및 모방 :

  • "추가"단추를 클릭 하기 전에 상황에 해당하는 양식 세트를 작성하십시오 .
  • 페이지를로드하고 소스를보고 모든 <input>필드를 기록하십시오.
  • "추가"단추를 클릭 한 상황에 맞게 양식 세트를 수정하십시오 (추가 필드 수 변경).
  • 페이지를로드하고 소스를보고 <input> 필드 변경 .
  • DOM을 이전 상태에서 이후 상태 로 이동하기에 적합한 방식으로 DOM을 수정하는 JavaScript를 작성하십시오 .
  • 해당 JavaScript를 "추가"버튼에 첨부하십시오.

폼 세트가 특수 숨겨진 <input>필드를 사용 하고 대본이 무엇을 해야하는지 아는 동안 머리 꼭대기에서 세부 사항을 기억하지는 않습니다. 위에서 설명한 것은 귀하의 상황에서 내가 할 일입니다.


stackoverflow.com/questions/62285767/… 을 도와 줄 수 있습니까? , 나는 많은 stackoverflow.com/questions/62285767 / ...을 시도했지만 답변을 얻지 못했습니다! 감사합니다
art_cs

6

이것에 대한 jquery 플러그인이 있으며, 장고 1.3에서 inline_form 세트와 함께 사용했으며 사전 채우기, 클라이언트 측 양식 추가, 제거 및 여러 inline_formsets를 포함하여 완벽하게 작동합니다.


링크 된 블로그 게시물이 여전히 존재하지만 다운로드 링크가 손상되었습니다. 분명히 플러그인은 @ elo80ka에 의해 작성되었으며, 그 답변 은 (예비?) 버전의 스크립트를 가리 킵니다.
lfurini

당신이 나를 도울 수 stackoverflow.com/questions/62285767/... , 내가 작정 시도했지만 답변을 얻을 didnt한다! 감사합니다
art_cs

4

하나의 옵션은 가능한 모든 양식으로 양식 세트를 작성하지만 처음에는 필요하지 않은 양식을 숨김으로 설정하는 것 display: none;입니다. 양식을 표시해야 할 경우 CSS 표시 block또는 적절한 것으로 설정하십시오.

"Ajax"가 수행하는 작업에 대한 자세한 내용을 알지 못하면 더 자세한 응답을하기가 어렵습니다.


4

필드를 선택적으로 살균 할 수있는 또 다른 cloneMore 버전. 여러 필드가 지워지지 않도록해야 할 때 사용하십시오.

$('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);
}

당신이 나를 도울 수 stackoverflow.com/questions/62285767/... , 내가 작정 시도했지만 답변을 얻을 didnt한다! 감사합니다
art_cs

2

cloneMore 함수에는 약간의 문제가 있습니다. 또한 django 자동 생성 된 숨겨진 필드의 값을 정리하기 때문에 하나 이상의 빈 양식으로 양식 세트를 저장하려고하면 django가 불평하게됩니다.

다음은 수정 사항입니다.

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

당신이 나를 도울 수 stackoverflow.com/questions/62285767/... , 내가 작정 시도했지만 답변을 얻을 didnt한다! 감사합니다
art_cs


2

위의 솔루션을 조금 더 잘 이해하기 위해 사냥 자원을 가지고있는 코더에게는

장고 다이내믹 폼셋

위의 링크를 읽은 후 Django 설명서와 이전 솔루션이 훨씬 더 합리적입니다.

장고 폼셋 문서

내가 혼란스러워 한 것에 대한 간단한 요약으로 : 관리 양식에는 양식의 개요가 포함되어 있습니다. Django가 추가 한 양식을 인식하려면 해당 정보를 정확하게 유지해야합니다. (커뮤니티, 내 문구 중 일부가 여기에 없으면 제안 해주십시오. Django를 처음 사용합니다.)


1

@ 파올로 베르 간 티노

연결된 모든 핸들러를 복제하려면 라인을 수정하십시오.

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

...에 대한

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

이 문제 를 방지하기 위해 .


당신이 나를 도울 수 stackoverflow.com/questions/62285767/... , 내가 작정 시도했지만 답변을 얻을 didnt한다! 감사합니다
art_cs

1

또한 제한된 수의 항목이있는 경우 HTML로 렌더링하는 것이 좋습니다. (그렇지 않으면 다른 방법을 사용해야합니다).

다음과 같이 숨길 수 있습니다.

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

그런 다음 js는 정말 간단합니다.

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

당신이 나를 도울 수 stackoverflow.com/questions/62285767/... , 내가 작정 시도했지만 답변을 얻을 didnt한다! 감사합니다
art_cs

1

위의 모든 답변은 jQuery를 사용하고 약간 복잡한 것을 만들기 때문에 다음 스크립트를 작성했습니다.

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

먼저 auto_id 를 false로 설정해야 하므로 id와 name의 복제를 비활성화하십시오. 입력 이름은이 형식에서 고유해야하므로 모든 식별은 ID가 아닌 입력으로 수행됩니다. 또한 교체해야 할 form, type그리고 해당 formset의 컨테이너입니다. (위의 예에서 choices)


당신이 나를 도울 수 stackoverflow.com/questions/62285767/... , 내가 작정 시도했지만 답변을 얻을 didnt한다! 감사합니다
art_cs
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.