Fixed #31524 -- Removed minified static assets from the admin.

This commit is contained in:
Jon Dufresne 2020-04-25 14:26:43 -07:00 committed by Carlton Gibson
parent 6c19c2ca51
commit 81ffedaacc
12 changed files with 15 additions and 122 deletions

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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):

View File

@ -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()})};

View File

@ -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)})});

View File

@ -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)}})})};

View File

@ -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)})}};

View File

@ -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/

View File

@ -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

View File

@ -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')

View File

@ -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')