From 736b751ed941899e1c46af83b09e207d97f415d7 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 7 Apr 2010 23:37:48 +0000 Subject: [PATCH] Fixed #13068, #9264, #9983, #9784 - regression with pre-populated fields and javascript inlines, and related bugs. Thanks to hejsan for the report, and to Sean Brant and Carl Meyer for the patch. #13068 is a regression caused by the new javascript inline forms in the admin. The others were existing javascript bugs with prepopulated fields. Since the solution depends on jQuery and would likely be very hard to backport without it, it will not be backported to 1.1.X. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12937 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin/media/js/admin/DateTimeShortcuts.js | 12 +++++- django/contrib/admin/media/js/inlines.js | 41 +++++++++++++------ django/contrib/admin/media/js/inlines.min.js | 11 ++--- django/contrib/admin/media/js/prepopulate.js | 34 +++++++++++++++ django/contrib/admin/options.py | 2 + .../templates/admin/edit_inline/stacked.html | 12 ++++++ .../templates/admin/edit_inline/tabular.html | 12 ++++++ .../admin/prepopulated_fields_js.html | 22 +++++++--- 8 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 django/contrib/admin/media/js/prepopulate.js diff --git a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js index fbaa599c7a..d840f78729 100644 --- a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js +++ b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js @@ -120,6 +120,7 @@ var DateTimeShortcuts = { }, handleClockQuicklink: function(num, val) { DateTimeShortcuts.clockInputs[num].value = val; + DateTimeShortcuts.clockInputs[num].focus(); DateTimeShortcuts.dismissClock(num); }, // Add calendar widget to a given field. @@ -247,12 +248,21 @@ var DateTimeShortcuts = { format = format.replace('\n', '\\n'); format = format.replace('\t', '\\t'); format = format.replace("'", "\\'"); - return "function(y, m, d) { DateTimeShortcuts.calendarInputs["+num+"].value = new Date(y, m-1, d).strftime('"+format+"');document.getElementById(DateTimeShortcuts.calendarDivName1+"+num+").style.display='none';}"; + return ["function(y, m, d) { DateTimeShortcuts.calendarInputs[", + num, + "].value = new Date(y, m-1, d).strftime('", + format, + "');DateTimeShortcuts.calendarInputs[", + num, + "].focus();document.getElementById(DateTimeShortcuts.calendarDivName1+", + num, + ").style.display='none';}"].join(''); }, handleCalendarQuickLink: function(num, offset) { var d = new Date(); d.setDate(d.getDate() + offset) DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]); + DateTimeShortcuts.calendarInputs[num].focus(); DateTimeShortcuts.dismissCalendar(num); }, cancelEventPropagation: function(e) { diff --git a/django/contrib/admin/media/js/inlines.js b/django/contrib/admin/media/js/inlines.js index 55118dfc4b..9decaae444 100644 --- a/django/contrib/admin/media/js/inlines.js +++ b/django/contrib/admin/media/js/inlines.js @@ -55,24 +55,41 @@ var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); var nextIndex = parseInt(totalForms.val()); var template = $("#" + options.prefix + "-empty"); - var row = template.clone(true).get(0); - $(row).removeClass(options.emptyCssClass).removeAttr("id").insertBefore($(template)); - $(row).html($(row).html().replace(/__prefix__/g, nextIndex)); - $(row).addClass(options.formCssClass).attr("id", options.prefix + (nextIndex + 1)); - if ($(row).is("TR")) { + var row = template.clone(true); + row.removeClass(options.emptyCssClass) + .addClass(options.formCssClass) + .attr("id", options.prefix + nextIndex) + .insertBefore($(template)); + row.find("*") + .filter(function() { + var el = $(this); + return el.attr("id") && el.attr("id").search(/__prefix__/) >= 0; + }).each(function() { + var el = $(this); + el.attr("id", el.attr("id").replace(/__prefix__/g, nextIndex)); + }) + .end() + .filter(function() { + var el = $(this); + return el.attr("name") && el.attr("name").search(/__prefix__/) >= 0; + }).each(function() { + var el = $(this); + el.attr("name", el.attr("name").replace(/__prefix__/g, 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('
' + options.deleteText + "
"); - } else if ($(row).is("UL") || $(row).is("OL")) { + row.children(":last").append('
' + options.deleteText + "
"); + } else if (row.is("ul") || row.is("ol")) { // If they're laid out as an ordered/unordered list, // insert an
  • after the last list item: - $(row).append('
  • ' + options.deleteText + "
  • "); + row.append('
  • ' + options.deleteText + "
  • "); } else { // Otherwise, just insert the remove button as the // last child element of the form's container: - $(row).children(":first").append('' + options.deleteText + ""); + row.children(":first").append('' + options.deleteText + ""); } - $(row).find("input,select,textarea,label,a").each(function() { + row.find("input,select,textarea,label,a").each(function() { updateElementIndex(this, options.prefix, totalForms.val()); }); // Update number of total forms @@ -82,7 +99,7 @@ addButton.parent().hide(); } // The delete button of each row triggers a bunch of other things - $(row).find("a." + options.deleteCssClass).click(function() { + row.find("a." + options.deleteCssClass).click(function() { // Remove the parent form containing this button: var row = $(this).parents("." + options.formCssClass); row.remove(); @@ -109,7 +126,7 @@ }); // If a post-add callback was supplied, call it with the added form: if (options.added) { - options.added($(row)); + options.added(row); } return false; }); diff --git a/django/contrib/admin/media/js/inlines.min.js b/django/contrib/admin/media/js/inlines.min.js index 29e048df54..891cc3fca5 100644 --- a/django/contrib/admin/media/js/inlines.min.js +++ b/django/contrib/admin/media/js/inlines.min.js @@ -1,5 +1,6 @@ -(function(a){a.fn.formset=function(f){var b=a.extend({},a.fn.formset.defaults,f),l=function(d,e,j){var c=new RegExp("("+e+"-\\d+)");e=e+"-"+j;a(d).attr("for")&&a(d).attr("for",a(d).attr("for").replace(c,e));if(d.id)d.id=d.id.replace(c,e);if(d.name)d.name=d.name.replace(c,e)};f=a("#id_"+b.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var h=a("#id_"+b.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");f=h.val()==""||h.val()-f.val()>0;a(this).each(function(){a(this).not("."+b.emptyCssClass).addClass(b.formCssClass)}); -if(a(this).length&&f){var i;if(a(this).attr("tagName")=="TR"){f=this.eq(0).children().length;a(this).parent().append(''+b.addText+"");i=a(this).parent().find("tr:last a")}else{a(this).filter(":last").after('
    '+b.addText+"
    ");i=a(this).filter(":last").next().find("a")}i.click(function(){var d=a("#id_"+b.prefix+"-TOTAL_FORMS"),e=parseInt(d.val()), -j=a("#"+b.prefix+"-empty"),c=j.clone(true).get(0);a(c).removeClass(b.emptyCssClass).removeAttr("id").insertBefore(a(j));a(c).html(a(c).html().replace(/__prefix__/g,e));a(c).addClass(b.formCssClass).attr("id",b.prefix+(e+1));if(a(c).is("TR"))a(c).children(":last").append('
    '+b.deleteText+"
    ");else a(c).is("UL")||a(c).is("OL")?a(c).append('
  • '+b.deleteText+"
  • "): -a(c).children(":first").append(''+b.deleteText+"");a(c).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,d.val())});a(d).val(e+1);h.val()!=""&&h.val()<=d.val()&&i.parent().hide();a(c).find("a."+b.deleteCssClass).click(function(){var g=a(this).parents("."+b.formCssClass);g.remove();b.removed&&b.removed(g);g=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(g.length);if(h.val()==""||h.val()>=g.length)i.parent().show(); -for(var k=0,m=g.length;k0;a(this).each(function(){a(this).not("."+b.emptyCssClass).addClass(b.formCssClass)}); +if(a(this).length&&g){var i;if(a(this).attr("tagName")=="TR"){g=this.eq(0).children().length;a(this).parent().append(''+b.addText+"");i=a(this).parent().find("tr:last a")}else{a(this).filter(":last").after('");i=a(this).filter(":last").next().find("a")}i.click(function(){var e=a("#id_"+b.prefix+"-TOTAL_FORMS"),f=parseInt(e.val()), +j=a("#"+b.prefix+"-empty"),d=j.clone(true);d.removeClass(b.emptyCssClass).addClass(b.formCssClass).attr("id",b.prefix+f).insertBefore(a(j));d.find("*").filter(function(){var c=a(this);return c.attr("id")&&c.attr("id").search(/__prefix__/)>=0}).each(function(){var c=a(this);c.attr("id",c.attr("id").replace(/__prefix__/g,f))}).end().filter(function(){var c=a(this);return c.attr("name")&&c.attr("name").search(/__prefix__/)>=0}).each(function(){var c=a(this);c.attr("name",c.attr("name").replace(/__prefix__/g, +f))});if(d.is("tr"))d.children(":last").append('
    '+b.deleteText+"
    ");else d.is("ul")||d.is("ol")?d.append('
  • '+b.deleteText+"
  • "):d.children(":first").append(''+b.deleteText+"");d.find("input,select,textarea,label,a").each(function(){l(this,b.prefix,e.val())});a(e).val(f+1);h.val()!=""&& +h.val()<=e.val()&&i.parent().hide();d.find("a."+b.deleteCssClass).click(function(){var c=a(this).parents("."+b.formCssClass);c.remove();b.removed&&b.removed(c);c=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(c.length);if(h.val()==""||h.val()>=c.length)i.parent().show();for(var k=0,m=c.length;k 0) { + values.push($(this).val()); + } + }); + field.val(URLify(values.join(' '), maxLength)); + }; + + dependencies.keyup(populate).change(populate).focus(populate); + }); + }; +})(jQuery.noConflict()); diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 6f66893c26..ae90bce067 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -274,6 +274,7 @@ class ModelAdmin(BaseModelAdmin): js.extend(['js/jquery.min.js', 'js/actions.min.js']) if self.prepopulated_fields: js.append('js/urlify.js') + js.append('js/prepopulate.js') if self.opts.get_ordered_objects(): js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) @@ -1201,6 +1202,7 @@ class InlineModelAdmin(BaseModelAdmin): js = ['js/jquery.min.js', 'js/inlines.min.js'] if self.prepopulated_fields: js.append('js/urlify.js') + js.append('js/prepopulate.js') if self.filter_vertical or self.filter_horizontal: js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index fb112a0166..6eb4492e49 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -48,6 +48,17 @@ }) } } + 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 = row.find(dependency_list.join(',')).find('input, select, textarea'); + if (dependencies.length) { + input.prepopulate(dependencies, input.attr('maxlength')); + } + }); + } $(rows).formset({ prefix: "{{ inline_admin_formset.formset.prefix }}", addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}", @@ -57,6 +68,7 @@ emptyCssClass: "empty-form", removed: updateInlineLabel, added: (function(row) { + initPrepopulatedFields(row); reinitDateTimeShortCuts(); updateSelectFilter(); updateInlineLabel(row); diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index dc9552f454..48be38db5d 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -94,6 +94,17 @@ }) } } + 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 = row.find(dependency_list.join(',')).find('input, select, textarea'); + if (dependencies.length) { + input.prepopulate(dependencies, input.attr('maxlength')); + } + }); + } $(rows).formset({ prefix: "{{ inline_admin_formset.formset.prefix }}", addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}", @@ -103,6 +114,7 @@ emptyCssClass: "empty-form", removed: alternatingRows, added: (function(row) { + initPrepopulatedFields(row); reinitDateTimeShortCuts(); updateSelectFilter(); alternatingRows(row); diff --git a/django/contrib/admin/templates/admin/prepopulated_fields_js.html b/django/contrib/admin/templates/admin/prepopulated_fields_js.html index e1cdb9614e..f0eb471dfb 100644 --- a/django/contrib/admin/templates/admin/prepopulated_fields_js.html +++ b/django/contrib/admin/templates/admin/prepopulated_fields_js.html @@ -1,11 +1,23 @@