summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Dufresne <jon.dufresne@gmail.com>2020-04-25 14:26:43 -0700
committerCarlton Gibson <carlton@noumenal.es>2020-05-14 16:07:56 +0200
commit81ffedaacc0d907b9feb73783edefdffd0ced606 (patch)
treeb7e2abdeef195eaab028d5c14fee8b9bff63843a
parent6c19c2ca51746f30fa35668876416c9178487388 (diff)
downloaddjango-81ffedaacc0d907b9feb73783edefdffd0ced606.tar.gz
Fixed #31524 -- Removed minified static assets from the admin.
-rw-r--r--MANIFEST.in1
-rw-r--r--django/contrib/admin/bin/compress.py52
-rw-r--r--django/contrib/admin/helpers.py4
-rw-r--r--django/contrib/admin/options.py9
-rw-r--r--django/contrib/admin/static/admin/js/actions.min.js7
-rw-r--r--django/contrib/admin/static/admin/js/collapse.min.js2
-rw-r--r--django/contrib/admin/static/admin/js/inlines.min.js11
-rw-r--r--django/contrib/admin/static/admin/js/prepopulate.min.js1
-rw-r--r--docs/internals/contributing/writing-code/javascript.txt27
-rw-r--r--docs/releases/3.1.txt5
-rw-r--r--tests/admin_inlines/tests.py2
-rw-r--r--tests/admin_views/tests.py16
12 files changed, 15 insertions, 122 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 0e131eb179..fecbae358b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -7,7 +7,6 @@ include MANIFEST.in
include package.json
include *.rst
graft django
-prune django/contrib/admin/bin
graft docs
graft extras
graft js_tests
diff --git a/django/contrib/admin/bin/compress.py b/django/contrib/admin/bin/compress.py
deleted file mode 100644
index 52ddcf5295..0000000000
--- a/django/contrib/admin/bin/compress.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env python
-import argparse
-import subprocess
-import sys
-from pathlib import Path
-
-js_path = Path(__file__).parents[1] / 'static' / 'admin' / 'js'
-
-
-def main():
- description = """With no file paths given this script will automatically
-compress files of the admin app. Requires the Google Closure Compiler library
-and Java version 7 or later."""
- parser = argparse.ArgumentParser(description=description)
- parser.add_argument('file', nargs='*')
- parser.add_argument("-v", "--verbose", action="store_true", dest="verbose")
- parser.add_argument("-q", "--quiet", action="store_false", dest="verbose")
- options = parser.parse_args()
-
- if not options.file:
- if options.verbose:
- sys.stdout.write("No filenames given; defaulting to admin scripts\n")
- files = [
- js_path / f
- for f in ["actions.js", "collapse.js", "inlines.js", "prepopulate.js"]
- ]
- else:
- files = [Path(f) for f in options.file]
-
- for file_path in files:
- to_compress = file_path.expanduser()
- if to_compress.exists():
- to_compress_min = to_compress.with_suffix('.min.js')
- cmd = ['npx']
- if not options.verbose:
- cmd.append('-q')
- cmd.extend([
- 'google-closure-compiler',
- '--language_out=ECMASCRIPT_2015',
- '--rewrite_polyfills=false',
- '--js', str(to_compress),
- '--js_output_file', str(to_compress_min),
- ])
- if options.verbose:
- sys.stdout.write("Running: %s\n" % ' '.join(cmd))
- subprocess.run(cmd)
- else:
- sys.stdout.write("File %s not found. Sure it exists?\n" % to_compress)
-
-
-if __name__ == '__main__':
- main()
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index 09dbd090fc..56d7970d2a 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -1,7 +1,6 @@
import json
from django import forms
-from django.conf import settings
from django.contrib.admin.utils import (
display_for_field, flatten_fieldsets, help_text_for_field, label_for_field,
lookup_field,
@@ -80,8 +79,7 @@ class Fieldset:
@property
def media(self):
if 'collapse' in self.classes:
- extra = '' if settings.DEBUG else '.min'
- return forms.Media(js=['admin/js/collapse%s.js' % extra])
+ return forms.Media(js=['admin/js/collapse.js'])
return forms.Media()
def __iter__(self):
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 90f14d2136..b895378d37 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -642,9 +642,9 @@ class ModelAdmin(BaseModelAdmin):
'jquery.init.js',
'core.js',
'admin/RelatedObjectLookups.js',
- 'actions%s.js' % extra,
+ 'actions.js',
'urlify.js',
- 'prepopulate%s.js' % extra,
+ 'prepopulate.js',
'vendor/xregexp/xregexp%s.js' % extra,
]
return forms.Media(js=['admin/js/%s' % url for url in js])
@@ -2024,12 +2024,11 @@ class InlineModelAdmin(BaseModelAdmin):
@property
def media(self):
extra = '' if settings.DEBUG else '.min'
- js = ['vendor/jquery/jquery%s.js' % extra, 'jquery.init.js',
- 'inlines%s.js' % extra]
+ js = ['vendor/jquery/jquery%s.js' % extra, 'jquery.init.js', 'inlines.js']
if self.filter_vertical or self.filter_horizontal:
js.extend(['SelectBox.js', 'SelectFilter2.js'])
if self.classes and 'collapse' in self.classes:
- js.append('collapse%s.js' % extra)
+ js.append('collapse.js')
return forms.Media(js=['admin/js/%s' % url for url in js])
def get_extra(self, request, obj=None, **kwargs):
diff --git a/django/contrib/admin/static/admin/js/actions.min.js b/django/contrib/admin/static/admin/js/actions.min.js
deleted file mode 100644
index 29fd0d8c2d..0000000000
--- a/django/contrib/admin/static/admin/js/actions.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';{const a=django.jQuery;let e;a.fn.actions=function(g){const b=a.extend({},a.fn.actions.defaults,g),f=a(this);let k=!1;const l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},n=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},
-p=function(){n();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},q=function(c){c?l():n();a(f).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){const c=a(f).filter(":checked").length,d=a(".action-counter").data("actionsIcnt");a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:d},!0));a(b.allToggle).prop("checked",function(){let a;c===f.length?(a=!0,l()):(a=!1,p());return a})};
-a(b.counterContainer).show();a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);h();1===a(b.acrossInput).val()&&m()});a(b.allToggle).show().on("click",function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).on("click",function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).on("click",function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});e=null;a(f).on("click",function(c){c||(c=window.event);
-const d=c.target?c.target:c.srcElement;if(e&&a.data(e)!==a.data(d)&&!0===c.shiftKey){let c=!1;a(e).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(f).each(function(){if(a.data(this)===a.data(e)||a.data(this)===a.data(d))c=c?!1:!0;c&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);e=d;h()});a("form#changelist-form table#result_list tr").on("change","td:gt(0) :input",
-function(){k=!0});a('form#changelist-form button[name="index"]').on("click",function(a){if(k)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').on("click",function(c){let d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return k?confirm(gettext("You have selected an action, but you haven\u2019t saved your changes to individual fields yet. Please click OK to save. You\u2019ll need to re-run the action.")):
-confirm(gettext("You have selected an action, and you haven\u2019t made any changes on individual fields. You\u2019re probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"};a(document).ready(function(){const g=
-a("tr input.action-select");0<g.length&&g.actions()})};
diff --git a/django/contrib/admin/static/admin/js/collapse.min.js b/django/contrib/admin/static/admin/js/collapse.min.js
deleted file mode 100644
index 06201c597f..0000000000
--- a/django/contrib/admin/static/admin/js/collapse.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-'use strict';window.addEventListener("load",function(){var c=document.querySelectorAll("fieldset.collapse");for(const [a,b]of c.entries())if(0===b.querySelectorAll("div.errors, ul.errorlist").length){b.classList.add("collapsed");c=b.querySelector("h2");const d=document.createElement("a");d.id="fieldsetcollapser"+a;d.className="collapse-toggle";d.href="#";d.textContent=gettext("Show");c.appendChild(document.createTextNode(" ("));c.appendChild(d);c.appendChild(document.createTextNode(")"))}const e=
-function(a){if(a.target.matches(".collapse-toggle")){a.preventDefault();a.stopPropagation();const b=a.target.closest("fieldset");b.classList.contains("collapsed")?(a.target.textContent=gettext("Hide"),b.classList.remove("collapsed")):(a.target.textContent=gettext("Show"),b.classList.add("collapsed"))}};document.querySelectorAll("fieldset.module").forEach(function(a){a.addEventListener("click",e)})});
diff --git a/django/contrib/admin/static/admin/js/inlines.min.js b/django/contrib/admin/static/admin/js/inlines.min.js
deleted file mode 100644
index fc6dddc6b3..0000000000
--- a/django/contrib/admin/static/admin/js/inlines.min.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';{const b=django.jQuery;b.fn.formset=function(c){const a=b.extend({},b.fn.formset.defaults,c),e=b(this),l=e.parent(),m=function(a,d,h){const 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");let n=parseInt(f.val(),10);const h=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),q=
-b("#id_"+a.prefix+"-MIN_NUM_FORMS").prop("autocomplete","off");let k;const t=function(g){g.preventDefault();g=b("#"+a.prefix+"-empty");const 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('<div><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></div>"):b.is("ul")||b.is("ol")?b.append('<li><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></li>"):b.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></span>");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<h.val()-d.length)&&k.parent().show();p(g);let c;f=function(){m(this,a.prefix,c)};c=0;for(g=d.length;c<g;c++)m(b(d).get(c),a.prefix,c),b(d.get(c)).find("*").each(f)},p=function(a){""!==q.val()&&0<=q.val()-f.val()?a.find(".inline-deletelink").hide():a.find(".inline-deletelink").show()};
-e.each(function(c){b(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});e.filter("."+a.formCssClass+":not(.has_original):not(."+a.emptyCssClass+")").each(function(){r(b(this))});p(e);k=a.addButton;(function(){if(null===k)if("TR"===e.prop("tagName")){const b=e.eq(-1).children().length;l.append('<tr class="'+a.addCssClass+'"><td colspan="'+b+'"><a href="#">'+a.addText+"</a></tr>");k=l.find("tr:last a")}else e.filter(":last").after('<div class="'+a.addCssClass+'"><a href="#">'+a.addText+"</a></div>"),
-k=e.filter(":last").next().find("a");k.on("click",t)})();c=""===h.val()||0<h.val()-f.val();e.length&&c?k.parent().show():k.parent().hide();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,addButton:null};b.fn.tabularFormset=function(c,a){c=b(this);const e=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,
-b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!0)}))},l=function(a){a.find(".prepopulated_field").each(function(){const c=b(this).find("input, select, textarea"),n=c.data("dependency_list")||[],h=[];b.each(n,function(b,c){h.push("#"+a.find(".field-"+c).find("input, select, textarea").attr("id"))});h.length&&c.prepopulate(h,c.attr("maxlength"))})};c.formset({prefix:a.prefix,addText:a.addText,
-formCssClass:"dynamic-"+a.prefix,deleteCssClass:"inline-deletelink",deleteText:a.deleteText,emptyCssClass:"empty-form",added:function(a){l(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());e()},addButton:a.addButton});return c};b.fn.stackedFormset=function(c,a){const e=b(this),l=function(a){b(c).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})},m=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,
-b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!0)}))},f=function(a){a.find(".prepopulated_field").each(function(){const c=b(this).find("input, select, textarea"),f=c.data("dependency_list")||[],e=[];b.each(f,function(b,c){e.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))});e.length&&c.prepopulate(e,c.attr("maxlength"))})};e.formset({prefix:a.prefix,
-addText:a.addText,formCssClass:"dynamic-"+a.prefix,deleteCssClass:"inline-deletelink",deleteText:a.deleteText,emptyCssClass:"empty-form",removed:l,added:function(a){f(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());m();l(a)},addButton:a.addButton});return e};b(document).ready(function(){b(".js-inline-admin-formset").each(function(){var c=b(this).data();const a=c.inlineFormset;switch(c.inlineType){case "stacked":c=a.name+"-group .inline-related";
-b(c).stackedFormset(c,a.options);break;case "tabular":c=a.name+"-group .tabular.inline-related tbody:first > tr.form-row",b(c).tabularFormset(c,a.options)}})})};
diff --git a/django/contrib/admin/static/admin/js/prepopulate.min.js b/django/contrib/admin/static/admin/js/prepopulate.min.js
deleted file mode 100644
index 11ead49905..0000000000
--- a/django/contrib/admin/static/admin/js/prepopulate.min.js
+++ /dev/null
@@ -1 +0,0 @@
-'use strict';{const b=django.jQuery;b.fn.prepopulate=function(d,f,g){return this.each(function(){const a=b(this),h=function(){if(!a.data("_changed")){var e=[];b.each(d,function(a,c){c=b(c);0<c.val().length&&e.push(c.val())});a.val(URLify(e.join(" "),f,g))}};a.data("_changed",!1);a.on("change",function(){a.data("_changed",!0)});if(!a.val())b(d.join(",")).on("keyup change focus",h)})}};
diff --git a/docs/internals/contributing/writing-code/javascript.txt b/docs/internals/contributing/writing-code/javascript.txt
index 3b90622f7e..8dbd6e34f5 100644
--- a/docs/internals/contributing/writing-code/javascript.txt
+++ b/docs/internals/contributing/writing-code/javascript.txt
@@ -38,32 +38,6 @@ JavaScript patches
Django's admin system leverages the jQuery framework to increase the
capabilities of the admin interface. In conjunction, there is an emphasis on
admin JavaScript performance and minimizing overall admin media file size.
-Serving compressed or "minified" versions of JavaScript files is considered
-best practice in this regard.
-
-To that end, patches for JavaScript files should include both the original
-code for future development (e.g. ``foo.js``), and a compressed version for
-production use (e.g. ``foo.min.js``). Any links to the file in the codebase
-should point to the compressed version.
-
-Compressing JavaScript
-----------------------
-
-To simplify the process of providing optimized JavaScript code, Django
-includes a handy Python script which should be used to create a "minified"
-version. To run it:
-
-.. console::
-
- $ python django/contrib/admin/bin/compress.py
-
-Behind the scenes, ``compress.py`` is a front-end for Google's
-`Closure Compiler`_ which is written in Java. The Closure Compiler library is
-not bundled with Django, but will be installed automatically by ``npm``. The
-Closure Compiler library requires `Java`_ 7 or higher.
-
-Please don't forget to run ``compress.py`` and include the ``diff`` of the
-minified scripts when submitting patches for Django's JavaScript.
.. _javascript-tests:
@@ -143,7 +117,6 @@ Then run the tests with:
$ npm test
-.. _Closure Compiler: https://developers.google.com/closure/compiler/
.. _EditorConfig: https://editorconfig.org/
.. _Java: https://www.java.com
.. _eslint: https://eslint.org/
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index 1b2c1fee34..934929637f 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -673,6 +673,11 @@ Miscellaneous
* The undocumented ``django.contrib.postgres.fields.jsonb.JsonAdapter`` class
is removed.
+* Minified JavaScript files are no longer included with the admin. If you
+ require these files to be minified, consider using a third party app or
+ external build tool. The minified vendored JavaScript files packaged with the
+ admin (e.g. :ref:`jquery.min.js <contrib-admin-jquery>`) are still included.
+
.. _deprecated-features-3.1:
Features deprecated in 3.1
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index 91bb9465a8..119dd56e61 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -510,7 +510,7 @@ class TestInlineMedia(TestDataMixin, TestCase):
'my_awesome_inline_scripts.js',
'custom_number.js',
'admin/js/jquery.init.js',
- 'admin/js/inlines.min.js',
+ 'admin/js/inlines.js',
]
)
self.assertContains(response, 'my_awesome_inline_scripts.js')
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index eb6f009f03..ada1e886ae 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -1229,26 +1229,18 @@ class AdminJavaScriptTest(TestCase):
response = self.client.get(reverse('admin:admin_views_section_add'))
self.assertNotContains(response, 'vendor/jquery/jquery.js')
self.assertContains(response, 'vendor/jquery/jquery.min.js')
- self.assertNotContains(response, 'prepopulate.js')
- self.assertContains(response, 'prepopulate.min.js')
- self.assertNotContains(response, 'actions.js')
- self.assertContains(response, 'actions.min.js')
- self.assertNotContains(response, 'collapse.js')
- self.assertContains(response, 'collapse.min.js')
- self.assertNotContains(response, 'inlines.js')
- self.assertContains(response, 'inlines.min.js')
+ self.assertContains(response, 'prepopulate.js')
+ self.assertContains(response, 'actions.js')
+ self.assertContains(response, 'collapse.js')
+ self.assertContains(response, 'inlines.js')
with override_settings(DEBUG=True):
response = self.client.get(reverse('admin:admin_views_section_add'))
self.assertContains(response, 'vendor/jquery/jquery.js')
self.assertNotContains(response, 'vendor/jquery/jquery.min.js')
self.assertContains(response, 'prepopulate.js')
- self.assertNotContains(response, 'prepopulate.min.js')
self.assertContains(response, 'actions.js')
- self.assertNotContains(response, 'actions.min.js')
self.assertContains(response, 'collapse.js')
- self.assertNotContains(response, 'collapse.min.js')
self.assertContains(response, 'inlines.js')
- self.assertNotContains(response, 'inlines.min.js')
@override_settings(ROOT_URLCONF='admin_views.urls')