From eb77e80de01e658541d4fcc3b0b38783ce4e6a7e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 7 Mar 2020 09:35:59 -0800 Subject: [PATCH] Fixed #31349 -- Used :nth-child() CSS pseudo-class to style alternative rows in admin. --- .../contrib/admin/static/admin/css/base.css | 10 +++---- .../contrib/admin/static/admin/js/inlines.js | 7 ----- .../admin/static/admin/js/inlines.min.js | 22 ++++++++-------- .../templates/admin/change_list_results.html | 2 +- .../templates/admin/edit_inline/tabular.html | 2 +- docs/releases/3.1.txt | 3 +++ js_tests/admin/inlines.test.js | 26 ++++--------------- js_tests/tests.html | 6 ++--- tests/admin_changelist/tests.py | 2 +- tests/admin_inlines/tests.py | 14 ---------- .../article/change_list_results.html | 2 +- 11 files changed, 31 insertions(+), 65 deletions(-) diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index cb527f1618..913de658dd 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -284,14 +284,14 @@ tr.alt { background: #f6f6f6; } -.row1, .row-form-errors { +tr:nth-child(odd), .row-form-errors { background: #fff; } -.row2, -.row2 .errorlist, -.row1 + .row-form-errors, -.row1 + .row-form-errors .errorlist { +tr:nth-child(even), +tr:nth-child(even) .errorlist, +tr:nth-child(odd) + .row-form-errors, +tr:nth-child(odd) + .row-form-errors .errorlist { background: #f9f9f9; } diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index c91b853a6b..74101bb377 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -203,11 +203,6 @@ // Tabular inlines --------------------------------------------------------- $.fn.tabularFormset = function(selector, options) { var $rows = $(this); - var alternatingRows = function(row) { - $(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 @@ -254,12 +249,10 @@ deleteCssClass: "inline-deletelink", deleteText: options.deleteText, emptyCssClass: "empty-form", - removed: alternatingRows, added: function(row) { initPrepopulatedFields(row); reinitDateTimeShortCuts(); updateSelectFilter(); - alternatingRows(row); }, addButton: options.addButton }); diff --git a/django/contrib/admin/static/admin/js/inlines.min.js b/django/contrib/admin/static/admin/js/inlines.min.js index f5f951c824..ba67b03ac8 100644 --- a/django/contrib/admin/static/admin/js/inlines.min.js +++ b/django/contrib/admin/static/admin/js/inlines.min.js @@ -1,11 +1,11 @@ -(function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),k=c.parent(),n=function(a,e,l){var g=new RegExp("("+e+"-(\\d+|__prefix__))");e=e+"-"+l;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(g,e));a.id&&(a.id=a.id.replace(g,e));a.name&&(a.name=a.name.replace(g,e))},h=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),e=parseInt(h.val(),10),l=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),q=b("#id_"+a.prefix+"-MIN_NUM_FORMS").prop("autocomplete", -"off"),t=function(g){g.preventDefault();g=b("#"+a.prefix+"-empty");var f=g.clone(!0);f.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+e);r(f);f.find("*").each(function(){n(this,a.prefix,h.val())});f.insertBefore(b(g));b(h).val(parseInt(h.val(),10)+1);e+=1;""!==l.val()&&0>=l.val()-h.val()&&m.parent().hide();p(f.closest(".inline-group"));a.added&&a.added(f);b(document).trigger("formset:added",[f,a.prefix])},r=function(b){b.is("tr")?b.children(":last").append('
'+a.deleteText+"
"):b.is("ul")||b.is("ol")?b.append('
  • '+a.deleteText+"
  • "):b.children(":first").append(''+a.deleteText+"");b.find("a."+a.deleteCssClass).on("click",u.bind(this))},u=function(g){g.preventDefault();var f=b(g.target).closest("."+a.formCssClass);g=f.closest(".inline-group");var d=f.prev();d.length&&d.hasClass("row-form-errors")&&d.remove(); -f.remove();--e;a.removed&&a.removed(f);b(document).trigger("formset:removed",[f,a.prefix]);f=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(f.length);(""===l.val()||0'+a.addText+"");m=k.find("tr:last a")}else c.filter(":last").after('"),m=c.filter(":last").next().find("a");m.on("click",t)})();d=""===l.val()|| -0 tr.form-row",b(d).tabularFormset(d,a.options)}})})})(django.jQuery); +(function(b){b.fn.formset=function(c){var a=b.extend({},b.fn.formset.defaults,c),e=b(this),l=e.parent(),m=function(a,d,h){var g=new RegExp("("+d+"-(\\d+|__prefix__))");d=d+"-"+h;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(g,d));a.id&&(a.id=a.id.replace(g,d));a.name&&(a.name=a.name.replace(g,d))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),n=parseInt(f.val(),10),h=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),q=b("#id_"+a.prefix+"-MIN_NUM_FORMS").prop("autocomplete", +"off"),t=function(g){g.preventDefault();g=b("#"+a.prefix+"-empty");var d=g.clone(!0);d.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+n);r(d);d.find("*").each(function(){m(this,a.prefix,f.val())});d.insertBefore(b(g));b(f).val(parseInt(f.val(),10)+1);n+=1;""!==h.val()&&0>=h.val()-f.val()&&k.parent().hide();p(d.closest(".inline-group"));a.added&&a.added(d);b(document).trigger("formset:added",[d,a.prefix])},r=function(b){b.is("tr")?b.children(":last").append('
    '+a.deleteText+"
    "):b.is("ul")||b.is("ol")?b.append('
  • '+a.deleteText+"
  • "):b.children(":first").append(''+a.deleteText+"");b.find("a."+a.deleteCssClass).on("click",u.bind(this))},u=function(g){g.preventDefault();var d=b(g.target).closest("."+a.formCssClass);g=d.closest(".inline-group");var f=d.prev();f.length&&f.hasClass("row-form-errors")&&f.remove(); +d.remove();--n;a.removed&&a.removed(d);b(document).trigger("formset:removed",[d,a.prefix]);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(""===h.val()||0'+a.addText+"");k=l.find("tr:last a")}else e.filter(":last").after('"),k=e.filter(":last").next().find("a");k.on("click",t)})();c=""===h.val()|| +0 tr.form-row", +b(c).tabularFormset(c,a.options)}})})})(django.jQuery); diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html index a4a2618e04..7eeba04f7c 100644 --- a/django/contrib/admin/templates/admin/change_list_results.html +++ b/django/contrib/admin/templates/admin/change_list_results.html @@ -30,7 +30,7 @@ {% if result.form and result.form.non_field_errors %} {{ result.form.non_field_errors }} {% endif %} -{% for item in result %}{{ item }}{% endfor %} +{% for item in result %}{{ item }}{% endfor %} {% endfor %} diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 94f97d94b6..633a6d7cb8 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -25,7 +25,7 @@ {% if inline_admin_form.form.non_field_errors %} {{ inline_admin_form.form.non_field_errors }} {% endif %} - {% if inline_admin_form.original or inline_admin_form.show_url %}

    diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 88b355c9cf..d7a22479b3 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -500,6 +500,9 @@ Miscellaneous * :djadmin:`migrate` management command now runs the ``database`` system checks only for a database to migrate. +* The admin CSS classes ``row1`` and ``row2`` are removed in favor of + ``:nth-child(odd)`` and ``:nth-child(even)`` pseudo-classes. + .. _deprecated-features-3.1: Features deprecated in 3.1 diff --git a/js_tests/admin/inlines.test.js b/js_tests/admin/inlines.test.js index 582ede1465..7a04e870bb 100644 --- a/js_tests/admin/inlines.test.js +++ b/js_tests/admin/inlines.test.js @@ -28,14 +28,14 @@ QUnit.test('add form', function(assert) { var addButton = this.table.find('.add-row a'); assert.equal(addButton.text(), this.addText); addButton.click(); - assert.ok(this.table.find('#first-1').hasClass('row2')); + assert.ok(this.table.find('#first-1')); }); QUnit.test('added form has remove button', function(assert) { var addButton = this.table.find('.add-row a'); assert.equal(addButton.text(), this.addText); addButton.click(); - assert.equal(this.table.find('#first-1.row2 .inline-deletelink').length, 1); + assert.equal(this.table.find('#first-1 .inline-deletelink').length, 1); }); QUnit.test('add/remove form events', function(assert) { @@ -45,11 +45,11 @@ QUnit.test('add/remove form events', function(assert) { var addButton = this.table.find('.add-row a'); $document.on('formset:added', function(event, $row, formsetName) { assert.ok(true, 'event `formset:added` triggered'); - assert.equal(true, $row.is($('.row2'))); + assert.equal(true, $row.is('#first-1')); assert.equal(formsetName, 'first'); }); addButton.click(); - var deletedRow = $('.row2'); + var deletedRow = $('#first-1'); var deleteLink = this.table.find('.inline-deletelink'); $document.on('formset:removed', function(event, $row, formsetName) { assert.ok(true, 'event `formset:removed` triggered'); @@ -74,7 +74,7 @@ QUnit.test('existing add button', function(assert) { }); assert.equal(this.table.find('.add-row a').length, 0); addButton.click(); - assert.ok(this.table.find('#first-1').hasClass('row2')); + assert.ok(this.table.find('#first-1')); }); @@ -125,22 +125,6 @@ QUnit.test('removing a form-row also removed related row with non-field errors', assert.notOk(this.table.find('.row-form-errors').length); }); -QUnit.test('removing and adding a row keeps cycling row1 and row2 classes', function(assert) { - var $ = django.jQuery; - var tr = this.inlineRows.slice(1, 2); - var deleteLink = tr.find('a.inline-deletelink'); - var addLink = this.table.find('.add-row > td > a'); - assert.ok(this.table.find('tr.form-row:even').hasClass('row1')); - assert.ok(this.table.find('tr.form-row:odd').hasClass('row2')); - deleteLink.trigger($.Event('click', {target: deleteLink})); - assert.ok(this.table.find('tr.form-row:even').hasClass('row1')); - assert.ok(this.table.find('tr.form-row:odd').hasClass('row2')); - addLink.trigger($.Event('click', {target: addLink})); - assert.ok(this.table.find('tr.form-row:even').hasClass('row1')); - assert.ok(this.table.find('tr.form-row:odd').hasClass('row2')); -}); - - QUnit.module('admin.inlines: tabular formsets with max_num', { beforeEach: function() { var $ = django.jQuery; diff --git a/js_tests/tests.html b/js_tests/tests.html index 988b7e3a4c..6878dd5cda 100644 --- a/js_tests/tests.html +++ b/js_tests/tests.html @@ -28,7 +28,7 @@ - + @@ -47,7 +47,7 @@
    - + @@ -62,7 +62,7 @@ - + diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index fa29886160..1d55813e22 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -40,7 +40,7 @@ from .models import ( def build_tbody_html(pk, href, extra_fields): return ( - '' + '' '' diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 95eb7104ab..2ddad4f5e7 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -1205,20 +1205,6 @@ class SeleniumTests(AdminSeleniumTestCase): 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(self.live_server_url + reverse('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") - def test_collapsed_inlines(self): # Collapsed inlines have SHOW/HIDE links. self.admin_login(username='super', password='secret') diff --git a/tests/admin_views/templates/admin/admin_views/article/change_list_results.html b/tests/admin_views/templates/admin/admin_views/article/change_list_results.html index ca36f3abcf..775641233f 100644 --- a/tests/admin_views/templates/admin/admin_views/article/change_list_results.html +++ b/tests/admin_views/templates/admin/admin_views/article/change_list_results.html @@ -30,7 +30,7 @@ {% if result.form.non_field_errors %} {% endif %} -{% for item in result %}{{ item }}{% endfor %} +{% for item in result %}{{ item }}{% endfor %} {% endfor %}
    ' '
    {{ result.form.non_field_errors }}