Ajaxを使用してDjangoフォームセットに新しいフォームを自動的に追加したいので、ユーザーが「追加」ボタンをクリックすると、新しいフォーム(フォームセットの一部)をページに追加するJavaScriptが実行されます。
Ajaxを使用してDjangoフォームセットに新しいフォームを自動的に追加したいので、ユーザーが「追加」ボタンをクリックすると、新しいフォーム(フォームセットの一部)をページに追加するJavaScriptが実行されます。
回答:
これは私が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>
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);
}
それがすること:
cloneMore
selector
最初の引数として受け入れtype
、formsetの2 番目の引数として受け入れます。がselector
すべきことは、複製すべきものを渡すことです。この場合は、div.table:last
jQueryがのクラスを持つ最後のテーブルを検索するように渡しますtable
。新しいフォームの挿入先を決定する:last
ためにselector
も使用されるため、この部分は重要です。おそらく、残りのフォームの最後でそれを使いたいでしょう。type
我々は更新できるように、引数がありmanagement_form
、特に、フィールド、TOTAL_FORMS
だけでなく、実際のフォームフィールドを。たとえば、Client
モデルがいっぱいのフォームセットがある場合、管理フィールドのIDはとにid_clients-TOTAL_FORMS
なりid_clients-INITIAL_FORMS
ますが、フォームフィールドの形式はid_clients-N-fieldname
withになります。N
で始まるフォーム番号0
です。したがって、type
引数を指定すると、cloneMore
関数は現在存在するフォームの数を調べ、新しいフォーム内のすべての入力とラベルを調べて、id_clients-(N)-name
to id_clients-(N+1)-name
などのすべてのフィールド名/ IDを置き換えます。完了すると、TOTAL_FORMS
フィールドが更新されて新しいフォームが反映され、セットの最後に追加されます。
この機能は私にとって特に役立ちます。設定方法により、フォームセットにさらにフォームを提供したいときにアプリ全体で使用でき、複製する非表示の「テンプレート」フォームを用意する必要がないためです。私がそれに渡す限り、フォームセット名とフォームが配置されるフォーマットです。それが役に立てば幸い。
prefix
、Formsetオブジェクトのメンバーに値を割り当てる必要があることがわかりました。これは、関数のtype
引数と同じ値でなければなりませんcloneMore
。
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)
、クリーンな方法で、1つのフォームのみを取得します。これをビューで処理する方法を説明できますか?
empty_form
)をうまく利用しています。
Paoloの提案は、1つの警告、つまりブラウザの戻る/進むボタンで美しく機能します。
Paoloのスクリプトで作成された動的要素は、ユーザーが戻る/進むボタンを使用してフォームセットに戻った場合、レンダリングされません。一部の取引ブレーカーになる可能性がある問題。
例:
1)ユーザーが「追加」ボタンを使用してフォームセットに2つの新しいフォームを追加する
2)ユーザーがフォームにデータを入力し、フォームセットを送信します
3)ユーザーがブラウザの戻るボタンをクリックする
4)フォームセットは元のフォームに縮小され、動的に追加されたフォームはすべて存在しません
これは、Paoloのスクリプトの欠陥ではありません。しかし、dom操作とブラウザのキャッシュのある現実。
フォームの値をセッションに格納し、フォームセットが読み込まれて要素を再度作成し、セッションから値を再読み込みするときに、いくつかのajaxマジックを使用できると思います。しかし、同じユーザーにしたい肛門とフォームの複数のインスタンスに応じて、これは非常に複雑になる可能性があります。
これに対処するための良い提案がありますか?
ありがとう!
動的djangoフォームの次のソリューションを確認してください。
http://code.google.com/p/django-dynamic-formset/
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
どちらもjQueryを利用しており、django固有です。最初のものはもう少し洗練されているようで、優れたデモ付きのダウンロードを提供します。
模倣して模倣する:
<input>
フィールドをメモします。<input>
フィールドがどのように変更されたかをメモします。フォームセットが特別な隠し<input>
フィールドを使用していること、およびスクリプトが何をしなければならないかはほぼわかっていますが、頭の上の詳細を思い出しません。上記で説明したのは、あなたの状況で私が何をするかです。
このためのjqueryプラグインがあり、Django 1.3のinline_formセットで使用しました。事前入力、クライアント側フォームの追加、削除、および複数のinline_formsetsを含め、完全に機能します。
フィールドの選択的なサニタイズを可能にする別の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);
}
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);
}
これははるかに良い解決策だと思います。
Djangoで動的なフォームセットをどのように作成しますか?
クローンはしませんか?
上記のソリューションをもう少しよく理解するためにリソースを探しているプログラマーのために:
上記のリンクを読んだ後は、Djangoのドキュメントと以前の解決策がもっと理解できるはずです。
私が混乱していることの簡単な要約として:管理フォームには、フォームの概要が含まれています。追加したフォームをDjangoが認識できるようにするには、その情報を正確に保つ必要があります。(コミュニティ、私の言い回しの一部がここにない場合は、提案をお願いします。Djangoは初めてです。)
ええ、エントリの数が限られている場合は、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();
};
};
}
上記のすべての答えは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と名前の重複を無効にする必要があります。入力名はそこの形式で一意である必要があるため、すべての識別はIDではなくそれらで行われます。また、交換する必要がありform
、type
そしてフォームセットのコンテナ。(上記の例ではchoices
)