Skip to content

Commit d638cdc

Browse files
Thomas Graingertimgraham
Thomas Grainger
authored andcommitted
Fixed #25165 -- Removed inline JavaScript from the admin.
This allows setting a Content-Security-Policy HTTP header (refs django#15727). Special thanks to blighj, the original author of this patch.
1 parent 105028e commit d638cdc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+455
-275
lines changed

.eslintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"no-octal-escape": [2],
1515
"no-underscore-dangle": [2],
1616
"no-unused-vars": [2, {"vars": "local", "args": "none"}],
17-
"no-script-url": [1],
17+
"no-script-url": [2],
1818
"no-shadow": [2, {"hoist": "functions"}],
1919
"quotes": [0, "single"],
2020
"linebreak-style": [2, "unix"],

django/contrib/admin/actions.py

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def delete_selected(modeladmin, request, queryset):
7474
protected=protected,
7575
opts=opts,
7676
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
77+
media=modeladmin.media,
7778
)
7879

7980
request.current_app = modeladmin.admin_site.name

django/contrib/admin/helpers.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22

3+
import json
34
import warnings
45

56
from django import forms
@@ -18,7 +19,7 @@
1819
from django.utils.encoding import force_text, smart_text
1920
from django.utils.html import conditional_escape, format_html
2021
from django.utils.safestring import mark_safe
21-
from django.utils.translation import ugettext_lazy as _
22+
from django.utils.translation import ugettext, ugettext_lazy as _
2223

2324
ACTION_CHECKBOX_NAME = '_selected_action'
2425

@@ -276,6 +277,19 @@ def fields(self):
276277
'help_text': form_field.help_text,
277278
}
278279

280+
def inline_formset_data(self):
281+
verbose_name = self.opts.verbose_name
282+
return json.dumps({
283+
'name': '#%s' % self.formset.prefix,
284+
'options': {
285+
'prefix': self.formset.prefix,
286+
'addText': ugettext('Add another %(verbose_name)s') % {
287+
'verbose_name': capfirst(verbose_name),
288+
},
289+
'deleteText': ugettext('Remove'),
290+
}
291+
})
292+
279293
def _media(self):
280294
media = self.opts.media + self.formset.media
281295
for fs in self:

django/contrib/admin/options.py

+21-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import unicode_literals
22

33
import copy
4+
import json
45
import operator
56
from collections import OrderedDict
67
from functools import partial, reduce, update_wrapper
@@ -40,7 +41,7 @@
4041
from django.utils import six
4142
from django.utils.decorators import method_decorator
4243
from django.utils.encoding import force_text, python_2_unicode_compatible
43-
from django.utils.html import escape, escapejs, format_html
44+
from django.utils.html import escape, format_html
4445
from django.utils.http import urlencode, urlquote
4546
from django.utils.safestring import mark_safe
4647
from django.utils.text import capfirst, get_text_list
@@ -568,9 +569,9 @@ def media(self):
568569
extra = '' if settings.DEBUG else '.min'
569570
js = [
570571
'core.js',
571-
'admin/RelatedObjectLookups.js',
572572
'vendor/jquery/jquery%s.js' % extra,
573573
'jquery.init.js',
574+
'admin/RelatedObjectLookups.js',
574575
'actions%s.js' % extra,
575576
'urlify.js',
576577
'prepopulate%s.js' % extra,
@@ -1084,9 +1085,12 @@ def response_add(self, request, obj, post_url_continue=None):
10841085
else:
10851086
attr = obj._meta.pk.attname
10861087
value = obj.serializable_value(attr)
1087-
return SimpleTemplateResponse('admin/popup_response.html', {
1088+
popup_response_data = json.dumps({
10881089
'value': value,
1089-
'obj': obj,
1090+
'obj': six.text_type(obj),
1091+
})
1092+
return SimpleTemplateResponse('admin/popup_response.html', {
1093+
'popup_response_data': popup_response_data,
10901094
})
10911095

10921096
elif "_continue" in request.POST:
@@ -1132,11 +1136,14 @@ def response_change(self, request, obj):
11321136
# Retrieve the `object_id` from the resolved pattern arguments.
11331137
value = request.resolver_match.args[0]
11341138
new_value = obj.serializable_value(attr)
1135-
return SimpleTemplateResponse('admin/popup_response.html', {
1139+
popup_response_data = json.dumps({
11361140
'action': 'change',
1137-
'value': escape(value),
1138-
'obj': escapejs(obj),
1139-
'new_value': escape(new_value),
1141+
'value': value,
1142+
'obj': six.text_type(obj),
1143+
'new_value': new_value,
1144+
})
1145+
return SimpleTemplateResponse('admin/popup_response.html', {
1146+
'popup_response_data': popup_response_data,
11401147
})
11411148

11421149
opts = self.model._meta
@@ -1300,9 +1307,12 @@ def response_delete(self, request, obj_display, obj_id):
13001307
opts = self.model._meta
13011308

13021309
if IS_POPUP_VAR in request.POST:
1303-
return SimpleTemplateResponse('admin/popup_response.html', {
1310+
popup_response_data = json.dumps({
13041311
'action': 'delete',
1305-
'value': escape(obj_id),
1312+
'value': obj_id,
1313+
})
1314+
return SimpleTemplateResponse('admin/popup_response.html', {
1315+
'popup_response_data': popup_response_data,
13061316
})
13071317

13081318
self.message_user(request,
@@ -1332,6 +1342,7 @@ def render_delete_form(self, request, context):
13321342
context.update(
13331343
to_field_var=TO_FIELD_VAR,
13341344
is_popup_var=IS_POPUP_VAR,
1345+
media=self.media,
13351346
)
13361347

13371348
return TemplateResponse(request,

django/contrib/admin/static/admin/js/SelectFilter2.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@ Requires core.js, SelectBox.js and addevent.js.
7575
filter_input.id = field_id + '_input';
7676

7777
selector_available.appendChild(from_box);
78-
var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript:void(0);', 'id', field_id + '_add_all_link');
78+
var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_add_all_link');
7979
choose_all.className = 'selector-chooseall';
8080

8181
// <ul class="selector-chooser">
8282
var selector_chooser = quickElement('ul', selector_div);
8383
selector_chooser.className = 'selector-chooser';
84-
var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript:void(0);', 'id', field_id + '_add_link');
84+
var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', '#', 'id', field_id + '_add_link');
8585
add_link.className = 'selector-add';
86-
var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript:void(0);', 'id', field_id + '_remove_link');
86+
var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', '#', 'id', field_id + '_remove_link');
8787
remove_link.className = 'selector-remove';
8888

8989
// <div class="selector-chosen">
@@ -105,7 +105,7 @@ Requires core.js, SelectBox.js and addevent.js.
105105

106106
var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
107107
to_box.className = 'filtered';
108-
var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript:void(0);', 'id', field_id + '_remove_all_link');
108+
var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_remove_all_link');
109109
clear_all.className = 'selector-clearall';
110110

111111
from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
@@ -195,4 +195,12 @@ Requires core.js, SelectBox.js and addevent.js.
195195
}
196196
};
197197

198+
addEvent(window, 'load', function(e) {
199+
$('select.selectfilter, select.selectfilterstacked').each(function() {
200+
var $el = $(this),
201+
data = $el.data();
202+
SelectFilter.init($el.attr('id'), data.fieldName, parseInt(data.isStacked, 10));
203+
});
204+
});
205+
198206
})(django.jQuery);

django/contrib/admin/static/admin/js/actions.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*global _actions_icnt, gettext, interpolate, ngettext*/
1+
/*global gettext, interpolate, ngettext*/
22
(function($) {
33
'use strict';
44
var lastChecked;
@@ -41,12 +41,13 @@
4141
},
4242
updateCounter = function() {
4343
var sel = $(actionCheckboxes).filter(":checked").length;
44-
// _actions_icnt is defined in the generated HTML
44+
// data-actions-icnt is defined in the generated HTML
4545
// and contains the total amount of objects in the queryset
46+
var actions_icnt = $('.action-counter').data('actionsIcnt');
4647
$(options.counterContainer).html(interpolate(
4748
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
4849
sel: sel,
49-
cnt: _actions_icnt
50+
cnt: actions_icnt
5051
}, true));
5152
$(options.allToggle).prop("checked", function() {
5253
var value;
@@ -143,4 +144,10 @@
143144
allToggle: "#action-toggle",
144145
selectedClass: "selected"
145146
};
147+
$(document).ready(function() {
148+
var $actionsEls = $('tr input.action-select');
149+
if ($actionsEls.length > 0) {
150+
$actionsEls.actions();
151+
}
152+
});
146153
})(django.jQuery);

django/contrib/admin/static/admin/js/actions.min.js

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)