diff options
| author | Travis Swicegood <travis@domain51.com> | 2012-09-08 15:07:33 -0400 |
|---|---|---|
| committer | Julien Phalip <jphalip@gmail.com> | 2012-09-08 15:09:37 -0400 |
| commit | 4754f122dd9b41fc9b2dee3fa74e19fc384237ab (patch) | |
| tree | 455aae880672717b0582896a6bae427002874c8a | |
| parent | 6e2bb344e40dafdf462f6fb660837fa061faf549 (diff) | |
Moved the admin inline JS to new JS files for cleanliness.
| -rw-r--r-- | AUTHORS | 1 | ||||
| -rw-r--r-- | django/contrib/admin/options.py | 2 | ||||
| -rw-r--r-- | django/contrib/admin/static/admin/js/inlines.js | 372 | ||||
| -rw-r--r-- | django/contrib/admin/static/admin/js/inlines.min.js | 14 | ||||
| -rw-r--r-- | django/contrib/admin/templates/admin/edit_inline/stacked.html | 64 | ||||
| -rw-r--r-- | django/contrib/admin/templates/admin/edit_inline/tabular.html | 65 | ||||
| -rw-r--r-- | tests/regressiontests/admin_inlines/tests.py | 61 |
7 files changed, 338 insertions, 241 deletions
@@ -506,6 +506,7 @@ answer newbie questions, and generally made Django that much better: Johan C. Stöver <johan@nilling.nl> Nowell Strite <http://nowell.strite.org/> Thomas Stromberg <tstromberg@google.com> + Travis Swicegood <travis@domain51.com> Pascal Varet SuperJared Radek Švarz <http://www.svarz.cz/translate/> diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 081d00121b..67b59cc31c 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1456,8 +1456,10 @@ class InlineModelAdmin(BaseModelAdmin): return request.user.has_perm( self.opts.app_label + '.' + self.opts.get_delete_permission()) + class StackedInline(InlineModelAdmin): template = 'admin/edit_inline/stacked.html' + class TabularInline(InlineModelAdmin): template = 'admin/edit_inline/tabular.html' diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index c11af82f76..4dc9459ff3 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -9,128 +9,264 @@ * All rights reserved. * * 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 * See: http://www.opensource.org/licenses/bsd-license.php */ (function($) { - $.fn.formset = function(opts) { - var options = $.extend({}, $.fn.formset.defaults, opts); - var updateElementIndex = function(el, prefix, ndx) { - var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); - var replacement = prefix + "-" + ndx; - if ($(el).attr("for")) { - $(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); - } - if (el.id) { - el.id = el.id.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"); - // only show the add button if we are allowed to add more items, + $.fn.formset = function(opts) { + var options = $.extend({}, $.fn.formset.defaults, opts); + var $this = $(this); + var $parent = $this.parent(); + var updateElementIndex = function(el, prefix, ndx) { + var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); + var replacement = prefix + "-" + ndx; + if ($(el).attr("for")) { + $(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); + } + if (el.id) { + el.id = el.id.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"); + // only show the add button if we are allowed to add more items, // note that max_num = None translates to a blank string. - var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; - $(this).each(function(i) { - $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); - }); - if ($(this).length && showAddButton) { - var addButton; - if ($(this).attr("tagName") == "TR") { - // If forms are laid out as table rows, insert the - // "add" button in a new table row: - 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>"); - addButton = $(this).parent().find("tr:last a"); - } else { - // 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>"); - addButton = $(this).filter(":last").next().find("a"); - } - addButton.click(function(e) { - e.preventDefault(); - var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); - var template = $("#" + options.prefix + "-empty"); - var row = template.clone(true); - row.removeClass(options.emptyCssClass) - .addClass(options.formCssClass) - .attr("id", options.prefix + "-" + nextIndex); - if (row.is("tr")) { - // If the forms are laid out in table rows, insert - // 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>"); - } else if (row.is("ul") || row.is("ol")) { - // If they're laid out as an ordered/unordered list, - // insert an <li> after the last list item: - row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>"); - } else { - // Otherwise, just insert the remove button as the - // 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.find("*").each(function() { - updateElementIndex(this, options.prefix, totalForms.val()); - }); - // Insert the new form when it has been fully edited - row.insertBefore($(template)); - // Update number of total forms - $(totalForms).val(parseInt(totalForms.val(), 10) + 1); - nextIndex += 1; - // Hide add button in case we've hit the max, except we want to add infinitely - if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { - addButton.parent().hide(); - } - // The delete button of each row triggers a bunch of other things - row.find("a." + options.deleteCssClass).click(function(e) { - e.preventDefault(); - // Remove the parent form containing this button: - var row = $(this).parents("." + options.formCssClass); - row.remove(); - nextIndex -= 1; - // If a post-delete callback was provided, call it with the deleted form: - if (options.removed) { - options.removed(row); - } - // Update the TOTAL_FORMS form count. - var forms = $("." + options.formCssClass); - $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); - // Show add button again once we drop below max - if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { - addButton.parent().show(); - } - // Also, update names and ids for all remaining form controls - // so they remain in sequence: - for (var i=0, formCount=forms.length; i<formCount; i++) - { - updateElementIndex($(forms).get(i), options.prefix, i); - $(forms.get(i)).find("*").each(function() { - updateElementIndex(this, options.prefix, i); - }); - } - }); - // If a post-add callback was supplied, call it with the added form: - if (options.added) { - options.added(row); - } - }); - } - return this; - }; - /* Setup plugin defaults */ - $.fn.formset.defaults = { - prefix: "form", // The form prefix for your django formset - addText: "add another", // Text for the add link - deleteText: "remove", // Text for the delete link - addCssClass: "add-row", // CSS class applied to the add link - deleteCssClass: "delete-row", // CSS class applied to the delete link - emptyCssClass: "empty-row", // CSS class applied to the empty row - formCssClass: "dynamic-form", // CSS class applied to each form in a formset - added: null, // Function called each time a new form is added - removed: null // Function called each time a form is deleted - }; + var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; + $this.each(function(i) { + $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); + }); + if ($this.length && showAddButton) { + var addButton; + if ($this.attr("tagName") == "TR") { + // If forms are laid out as table rows, insert the + // "add" button in a new table row: + var numCols = this.eq(-1).children().length; + $parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>"); + addButton = $parent.find("tr:last a"); + } else { + // 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>"); + addButton = $this.filter(":last").next().find("a"); + } + addButton.click(function(e) { + e.preventDefault(); + var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); + var template = $("#" + options.prefix + "-empty"); + var row = template.clone(true); + row.removeClass(options.emptyCssClass) + .addClass(options.formCssClass) + .attr("id", options.prefix + "-" + nextIndex); + if (row.is("tr")) { + // If the forms are laid out in table rows, insert + // 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>"); + } else if (row.is("ul") || row.is("ol")) { + // If they're laid out as an ordered/unordered list, + // insert an <li> after the last list item: + row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>"); + } else { + // Otherwise, just insert the remove button as the + // 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.find("*").each(function() { + updateElementIndex(this, options.prefix, totalForms.val()); + }); + // Insert the new form when it has been fully edited + row.insertBefore($(template)); + // Update number of total forms + $(totalForms).val(parseInt(totalForms.val(), 10) + 1); + nextIndex += 1; + // Hide add button in case we've hit the max, except we want to add infinitely + if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { + addButton.parent().hide(); + } + // The delete button of each row triggers a bunch of other things + row.find("a." + options.deleteCssClass).click(function(e) { + e.preventDefault(); + // Remove the parent form containing this button: + var row = $(this).parents("." + options.formCssClass); + row.remove(); + nextIndex -= 1; + // If a post-delete callback was provided, call it with the deleted form: + if (options.removed) { + options.removed(row); + } + // Update the TOTAL_FORMS form count. + var forms = $("." + options.formCssClass); + $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); + // Show add button again once we drop below max + if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { + addButton.parent().show(); + } + // Also, update names and ids for all remaining form controls + // so they remain in sequence: + for (var i=0, formCount=forms.length; i<formCount; i++) + { + updateElementIndex($(forms).get(i), options.prefix, i); + $(forms.get(i)).find("*").each(function() { + updateElementIndex(this, options.prefix, i); + }); + } + }); + // If a post-add callback was supplied, call it with the added form: + if (options.added) { + options.added(row); + } + }); + } + return this; + }; + + /* Setup plugin defaults */ + $.fn.formset.defaults = { + prefix: "form", // The form prefix for your django formset + addText: "add another", // Text for the add link + deleteText: "remove", // Text for the delete link + addCssClass: "add-row", // CSS class applied to the add link + deleteCssClass: "delete-row", // CSS class applied to the delete link + emptyCssClass: "empty-row", // CSS class applied to the empty row + formCssClass: "dynamic-form", // CSS class applied to each form in a formset + 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); diff --git a/django/contrib/admin/static/admin/js/inlines.min.js b/django/contrib/admin/static/admin/js/inlines.min.js index 1a7bd2cdf8..d48ee0a098 100644 --- a/django/contrib/admin/static/admin/js/inlines.min.js +++ b/django/contrib/admin/static/admin/js/inlines.min.js @@ -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("."+ -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(); -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)">'+ -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,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); +(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(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+ +"-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.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,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); diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index d57d3dad49..2025dd8083 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -20,63 +20,11 @@ <script type="text/javascript"> (function($) { - $(document).ready(function() { - var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related"; - var updateInlineLabel = function(row) { - $(rows).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, "{% 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); - }) - }); - }); + $("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({ + prefix: '{{ inline_admin_formset.formset.prefix }}', + adminStaticPrefix: '{% static "admin/" %}', + deleteText: "{% trans "Remove" %}", + addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}" + }); })(django.jQuery); </script> diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 4f49153819..f2757ede48 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -67,64 +67,13 @@ </div> <script type="text/javascript"> + (function($) { - $(document).ready(function($) { - var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr"; - var alternatingRows = function(row) { - $(rows).not(".add-row").removeClass("row1 row2") - .filter(":even").addClass("row1").end() - .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); - }) - }); - }); + $("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({ + prefix: "{{ inline_admin_formset.formset.prefix }}", + adminStaticPrefix: '{% static "admin/" %}', + addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}", + deleteText: "{% trans 'Remove' %}" + }); })(django.jQuery); </script> diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py index 4f25d3dbfb..57f45ab0ff 100644 --- a/tests/regressiontests/admin_inlines/tests.py +++ b/tests/regressiontests/admin_inlines/tests.py @@ -8,10 +8,11 @@ from django.test import TestCase from django.test.utils import override_settings # local test models -from .admin import InnerInline +from .admin import InnerInline, TitleInline, site from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, 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',)) @@ -408,6 +409,47 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): fixtures = ['admin-views-users.xml'] 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): """ 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( '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): webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver' |
