Moved the admin inline JS to new JS files for cleanliness.

This commit is contained in:
Travis Swicegood 2012-09-08 15:07:33 -04:00 committed by Julien Phalip
parent 6e2bb344e4
commit 4754f122dd
7 changed files with 338 additions and 241 deletions

View File

@ -506,6 +506,7 @@ answer newbie questions, and generally made Django that much better:
Johan C. Stöver <johan@nilling.nl> Johan C. Stöver <johan@nilling.nl>
Nowell Strite <http://nowell.strite.org/> Nowell Strite <http://nowell.strite.org/>
Thomas Stromberg <tstromberg@google.com> Thomas Stromberg <tstromberg@google.com>
Travis Swicegood <travis@domain51.com>
Pascal Varet Pascal Varet
SuperJared SuperJared
Radek Švarz <http://www.svarz.cz/translate/> Radek Švarz <http://www.svarz.cz/translate/>

View File

@ -1456,8 +1456,10 @@ class InlineModelAdmin(BaseModelAdmin):
return request.user.has_perm( return request.user.has_perm(
self.opts.app_label + '.' + self.opts.get_delete_permission()) self.opts.app_label + '.' + self.opts.get_delete_permission())
class StackedInline(InlineModelAdmin): class StackedInline(InlineModelAdmin):
template = 'admin/edit_inline/stacked.html' template = 'admin/edit_inline/stacked.html'
class TabularInline(InlineModelAdmin): class TabularInline(InlineModelAdmin):
template = 'admin/edit_inline/tabular.html' template = 'admin/edit_inline/tabular.html'

View File

@ -9,128 +9,264 @@
* All rights reserved. * All rights reserved.
* *
* Spiced up with Code from Zain Memon's GSoC project 2009 * Spiced up with Code from Zain Memon's GSoC project 2009
* and modified for Django by Jannis Leidel * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip.
* *
* Licensed under the New BSD License * Licensed under the New BSD License
* See: http://www.opensource.org/licenses/bsd-license.php * See: http://www.opensource.org/licenses/bsd-license.php
*/ */
(function($) { (function($) {
$.fn.formset = function(opts) { $.fn.formset = function(opts) {
var options = $.extend({}, $.fn.formset.defaults, opts); var options = $.extend({}, $.fn.formset.defaults, opts);
var updateElementIndex = function(el, prefix, ndx) { var $this = $(this);
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); var $parent = $this.parent();
var replacement = prefix + "-" + ndx; var updateElementIndex = function(el, prefix, ndx) {
if ($(el).attr("for")) { var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); var replacement = prefix + "-" + ndx;
} if ($(el).attr("for")) {
if (el.id) { $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
el.id = el.id.replace(id_regex, replacement); }
} if (el.id) {
if (el.name) { el.id = el.id.replace(id_regex, replacement);
el.name = el.name.replace(id_regex, replacement); }
} if (el.name) {
}; el.name = el.name.replace(id_regex, replacement);
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); }
var nextIndex = parseInt(totalForms.val(), 10); };
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
// only show the add button if we are allowed to add more items, var nextIndex = parseInt(totalForms.val(), 10);
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
// only show the add button if we are allowed to add more items,
// note that max_num = None translates to a blank string. // note that max_num = None translates to a blank string.
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
$(this).each(function(i) { $this.each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass); $(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
}); });
if ($(this).length && showAddButton) { if ($this.length && showAddButton) {
var addButton; var addButton;
if ($(this).attr("tagName") == "TR") { if ($this.attr("tagName") == "TR") {
// If forms are laid out as table rows, insert the // If forms are laid out as table rows, insert the
// "add" button in a new table row: // "add" button in a new table row:
var numCols = this.eq(-1).children().length; var numCols = this.eq(-1).children().length;
$(this).parent().append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>"); $parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
addButton = $(this).parent().find("tr:last a"); addButton = $parent.find("tr:last a");
} else { } else {
// Otherwise, insert it immediately after the last form: // Otherwise, insert it immediately after the last form:
$(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>"); $this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
addButton = $(this).filter(":last").next().find("a"); addButton = $this.filter(":last").next().find("a");
} }
addButton.click(function(e) { addButton.click(function(e) {
e.preventDefault(); e.preventDefault();
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
var template = $("#" + options.prefix + "-empty"); var template = $("#" + options.prefix + "-empty");
var row = template.clone(true); var row = template.clone(true);
row.removeClass(options.emptyCssClass) row.removeClass(options.emptyCssClass)
.addClass(options.formCssClass) .addClass(options.formCssClass)
.attr("id", options.prefix + "-" + nextIndex); .attr("id", options.prefix + "-" + nextIndex);
if (row.is("tr")) { if (row.is("tr")) {
// If the forms are laid out in table rows, insert // If the forms are laid out in table rows, insert
// the remove button into the last table cell: // the remove button into the last table cell:
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>"); row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
} else if (row.is("ul") || row.is("ol")) { } else if (row.is("ul") || row.is("ol")) {
// If they're laid out as an ordered/unordered list, // If they're laid out as an ordered/unordered list,
// insert an <li> after the last list item: // insert an <li> after the last list item:
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>"); row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
} else { } else {
// Otherwise, just insert the remove button as the // Otherwise, just insert the remove button as the
// last child element of the form's container: // last child element of the form's container:
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>"); row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
} }
row.find("*").each(function() { row.find("*").each(function() {
updateElementIndex(this, options.prefix, totalForms.val()); updateElementIndex(this, options.prefix, totalForms.val());
}); });
// Insert the new form when it has been fully edited // Insert the new form when it has been fully edited
row.insertBefore($(template)); row.insertBefore($(template));
// Update number of total forms // Update number of total forms
$(totalForms).val(parseInt(totalForms.val(), 10) + 1); $(totalForms).val(parseInt(totalForms.val(), 10) + 1);
nextIndex += 1; nextIndex += 1;
// Hide add button in case we've hit the max, except we want to add infinitely // Hide add button in case we've hit the max, except we want to add infinitely
if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
addButton.parent().hide(); addButton.parent().hide();
} }
// The delete button of each row triggers a bunch of other things // The delete button of each row triggers a bunch of other things
row.find("a." + options.deleteCssClass).click(function(e) { row.find("a." + options.deleteCssClass).click(function(e) {
e.preventDefault(); e.preventDefault();
// Remove the parent form containing this button: // Remove the parent form containing this button:
var row = $(this).parents("." + options.formCssClass); var row = $(this).parents("." + options.formCssClass);
row.remove(); row.remove();
nextIndex -= 1; nextIndex -= 1;
// If a post-delete callback was provided, call it with the deleted form: // If a post-delete callback was provided, call it with the deleted form:
if (options.removed) { if (options.removed) {
options.removed(row); options.removed(row);
} }
// Update the TOTAL_FORMS form count. // Update the TOTAL_FORMS form count.
var forms = $("." + options.formCssClass); var forms = $("." + options.formCssClass);
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
// Show add button again once we drop below max // Show add button again once we drop below max
if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) {
addButton.parent().show(); addButton.parent().show();
} }
// Also, update names and ids for all remaining form controls // Also, update names and ids for all remaining form controls
// so they remain in sequence: // so they remain in sequence:
for (var i=0, formCount=forms.length; i<formCount; i++) for (var i=0, formCount=forms.length; i<formCount; i++)
{ {
updateElementIndex($(forms).get(i), options.prefix, i); updateElementIndex($(forms).get(i), options.prefix, i);
$(forms.get(i)).find("*").each(function() { $(forms.get(i)).find("*").each(function() {
updateElementIndex(this, options.prefix, i); updateElementIndex(this, options.prefix, i);
}); });
} }
}); });
// If a post-add callback was supplied, call it with the added form: // If a post-add callback was supplied, call it with the added form:
if (options.added) { if (options.added) {
options.added(row); options.added(row);
} }
}); });
} }
return this; return this;
}; };
/* Setup plugin defaults */
$.fn.formset.defaults = { /* Setup plugin defaults */
prefix: "form", // The form prefix for your django formset $.fn.formset.defaults = {
addText: "add another", // Text for the add link prefix: "form", // The form prefix for your django formset
deleteText: "remove", // Text for the delete link addText: "add another", // Text for the add link
addCssClass: "add-row", // CSS class applied to the add link deleteText: "remove", // Text for the delete link
deleteCssClass: "delete-row", // CSS class applied to the delete link addCssClass: "add-row", // CSS class applied to the add link
emptyCssClass: "empty-row", // CSS class applied to the empty row deleteCssClass: "delete-row", // CSS class applied to the delete link
formCssClass: "dynamic-form", // CSS class applied to each form in a formset emptyCssClass: "empty-row", // CSS class applied to the empty row
added: null, // Function called each time a new form is added formCssClass: "dynamic-form", // CSS class applied to each form in a formset
removed: null // Function called each time a form is deleted added: null, // Function called each time a new form is added
}; removed: null // Function called each time a form is deleted
};
// Tabular inlines ---------------------------------------------------------
$.fn.tabularFormset = function(options) {
var $rows = $(this);
var alternatingRows = function(row) {
$($rows.selector).not(".add-row").removeClass("row1 row2")
.filter(":even").addClass("row1").end()
.filter(":odd").addClass("row2");
};
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
var updateSelectFilter = function() {
// If any SelectFilter widgets are a part of the new form,
// instantiate a new SelectFilter instance for it.
if (typeof SelectFilter != 'undefined'){
$('.selectfilter').each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix );
});
$('.selectfilterstacked').each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix );
});
}
};
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this),
input = field.find('input, select, textarea'),
dependency_list = input.data('dependency_list') || [],
dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
$rows.formset({
prefix: options.prefix,
addText: options.addText,
formCssClass: "dynamic-" + options.prefix,
deleteCssClass: "inline-deletelink",
deleteText: options.deleteText,
emptyCssClass: "empty-form",
removed: alternatingRows,
added: function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
alternatingRows(row);
}
});
return $rows;
};
// Stacked inlines ---------------------------------------------------------
$.fn.stackedFormset = function(options) {
var $rows = $(this);
var updateInlineLabel = function(row) {
$($rows.selector).find(".inline_label").each(function(i) {
var count = i + 1;
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
});
};
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force, yuck.
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
var updateSelectFilter = function() {
// If any SelectFilter widgets were added, instantiate a new instance.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix);
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix);
});
}
};
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this),
input = field.find('input, select, textarea'),
dependency_list = input.data('dependency_list') || [],
dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
$rows.formset({
prefix: options.prefix,
addText: options.addText,
formCssClass: "dynamic-" + options.prefix,
deleteCssClass: "inline-deletelink",
deleteText: options.deleteText,
emptyCssClass: "empty-form",
removed: updateInlineLabel,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
updateInlineLabel(row);
})
});
return $rows;
};
})(django.jQuery); })(django.jQuery);

View File

@ -1,5 +1,9 @@
(function(b){b.fn.formset=function(g){var a=b.extend({},b.fn.formset.defaults,g),k=function(c,f,e){var d=RegExp("("+f+"-(\\d+|__prefix__))");f=f+"-"+e;b(c).attr("for")&&b(c).attr("for",b(c).attr("for").replace(d,f));if(c.id)c.id=c.id.replace(d,f);if(c.name)c.name=c.name.replace(d,f)};g=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var l=parseInt(g.val(),10),h=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");g=h.val()===""||h.val()-g.val()>0;b(this).each(function(){b(this).not("."+ (function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),d=c.parent(),i=function(a,e,g){var d=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+g;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),g=parseInt(f.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),f=""===e.val()||0<e.val()-f.val();c.each(function(){b(this).not("."+
a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&g){var j;if(b(this).attr("tagName")=="TR"){g=this.eq(-1).children().length;b(this).parent().append('<tr class="'+a.addCssClass+'"><td colspan="'+g+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>");j=b(this).parent().find("tr:last a")}else{b(this).filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>");j=b(this).filter(":last").next().find("a")}j.click(function(c){c.preventDefault(); a.emptyCssClass).addClass(a.formCssClass)});if(c.length&&f){var h;"TR"==c.attr("tagName")?(c=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),h=d.find("tr:last a")):(c.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+
var f=b("#id_"+a.prefix+"-TOTAL_FORMS");c=b("#"+a.prefix+"-empty");var e=c.clone(true);e.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);if(e.is("tr"))e.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>");else e.is("ul")||e.is("ol")?e.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):e.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+ "-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):c.is("ul")||c.is("ol")?c.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):c.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");c.find("*").each(function(){i(this,
a.deleteText+"</a></span>");e.find("*").each(function(){k(this,a.prefix,f.val())});e.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);l+=1;h.val()!==""&&h.val()-f.val()<=0&&j.parent().hide();e.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();l-=1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);if(h.val()===""||h.val()-d.length>0)j.parent().show();for(var i=0,m=d.length;i<m;i++){k(b(d).get(i), a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c<f;c++){i(b(d).get(c),a.prefix,c);b(d.get(c)).find("*").each(function(){i(this,
a.prefix,i);b(d.get(i)).find("*").each(function(){k(this,a.prefix,i)})}});a.added&&a.added(e)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(django.jQuery); a.prefix,c)})}});a.added&&a.added(c)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null};b.fn.tabularFormset=function(d){var a=b(this),c=function(){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+
d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());"undefined"!=
typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a};b.fn.stackedFormset=function(d){var a=b(this),c=function(){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})};a.formset({prefix:d.prefix,
addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".form-row .field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),
DateTimeShortcuts.init());"undefined"!=typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a}})(django.jQuery);

View File

@ -20,63 +20,11 @@
<script type="text/javascript"> <script type="text/javascript">
(function($) { (function($) {
$(document).ready(function() { $("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related"; prefix: '{{ inline_admin_formset.formset.prefix }}',
var updateInlineLabel = function(row) { adminStaticPrefix: '{% static "admin/" %}',
$(rows).find(".inline_label").each(function(i) { deleteText: "{% trans "Remove" %}",
var count = i + 1; addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}"
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); });
});
};
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force, yuck.
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
var updateSelectFilter = function() {
// If any SelectFilter widgets were added, instantiate a new instance.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
});
}
};
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this);
var input = field.find('input, select, textarea');
var dependency_list = input.data('dependency_list') || [];
var dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: updateInlineLabel,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
updateInlineLabel(row);
})
});
});
})(django.jQuery); })(django.jQuery);
</script> </script>

View File

@ -67,64 +67,13 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
(function($) { (function($) {
$(document).ready(function($) { $("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr"; prefix: "{{ inline_admin_formset.formset.prefix }}",
var alternatingRows = function(row) { adminStaticPrefix: '{% static "admin/" %}',
$(rows).not(".add-row").removeClass("row1 row2") addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
.filter(":even").addClass("row1").end() deleteText: "{% trans 'Remove' %}"
.filter(rows + ":odd").addClass("row2"); });
}
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
}
var updateSelectFilter = function() {
// If any SelectFilter widgets are a part of the new form,
// instantiate a new SelectFilter instance for it.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
});
}
}
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this);
var input = field.find('input, select, textarea');
var dependency_list = input.data('dependency_list') || [];
var dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
}
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: alternatingRows,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
alternatingRows(row);
})
});
});
})(django.jQuery); })(django.jQuery);
</script> </script>

View File

@ -8,10 +8,11 @@ from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
# local test models # local test models
from .admin import InnerInline from .admin import InnerInline, TitleInline, site
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile, OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2) ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
Title)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@ -408,6 +409,47 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
fixtures = ['admin-views-users.xml'] fixtures = ['admin-views-users.xml']
urls = "regressiontests.admin_inlines.urls" urls = "regressiontests.admin_inlines.urls"
def test_add_stackeds(self):
"""
Ensure that the "Add another XXX" link correctly adds items to the
stacked formset.
"""
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/holder4/add/'))
inline_id = '#inner4stacked_set-group'
rows_length = lambda: len(self.selenium.find_elements_by_css_selector(
'%s .dynamic-inner4stacked_set' % inline_id))
self.assertEqual(rows_length(), 3)
add_button = self.selenium.find_element_by_link_text(
'Add another Inner4 Stacked')
add_button.click()
self.assertEqual(rows_length(), 4)
def test_delete_stackeds(self):
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/holder4/add/'))
inline_id = '#inner4stacked_set-group'
rows_length = lambda: len(self.selenium.find_elements_by_css_selector(
'%s .dynamic-inner4stacked_set' % inline_id))
self.assertEqual(rows_length(), 3)
add_button = self.selenium.find_element_by_link_text(
'Add another Inner4 Stacked')
add_button.click()
add_button.click()
self.assertEqual(rows_length(), 5, msg="sanity check")
for delete_link in self.selenium.find_elements_by_css_selector(
'%s .inline-deletelink' % inline_id):
delete_link.click()
self.assertEqual(rows_length(), 3)
def test_add_inlines(self): def test_add_inlines(self):
""" """
Ensure that the "Add another XXX" link correctly adds items to the Ensure that the "Add another XXX" link correctly adds items to the
@ -516,6 +558,21 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
self.assertEqual(len(self.selenium.find_elements_by_css_selector( self.assertEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1) 'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
def test_alternating_rows(self):
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/profilecollection/add/'))
# Add a few inlines
self.selenium.find_element_by_link_text('Add another Profile').click()
self.selenium.find_element_by_link_text('Add another Profile').click()
row_selector = 'form#profilecollection_form tr.dynamic-profile_set'
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
"%s.row1" % row_selector)), 2, msg="Expect two row1 styled rows")
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
"%s.row2" % row_selector)), 1, msg="Expect one row2 styled row")
class SeleniumChromeTests(SeleniumFirefoxTests): class SeleniumChromeTests(SeleniumFirefoxTests):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver' webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'