Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

  • Loading branch information...
commit 4754f122dd9b41fc9b2dee3fa74e19fc384237ab 1 parent 6e2bb34
@tswicegood tswicegood authored jphalip committed
View
1  AUTHORS
@@ -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/>
View
2  django/contrib/admin/options.py
@@ -1456,8 +1456,10 @@ def has_delete_permission(self, request, obj=None):
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'
View
372 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);
View
14 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);
View
64 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>
View
65 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>
View
61 tests/regressiontests/admin_inlines/tests.py
@@ -8,10 +8,11 @@
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 @@ def test_delete_inlines(self):
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'
Please sign in to comment.
Something went wrong with that request. Please try again.