mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
Fixed #25165 -- Removed inline JavaScript from the admin.
This allows setting a Content-Security-Policy HTTP header (refs #15727). Special thanks to blighj, the original author of this patch.
This commit is contained in:
parent
105028eec6
commit
d638cdc42a
@ -14,7 +14,7 @@
|
||||
"no-octal-escape": [2],
|
||||
"no-underscore-dangle": [2],
|
||||
"no-unused-vars": [2, {"vars": "local", "args": "none"}],
|
||||
"no-script-url": [1],
|
||||
"no-script-url": [2],
|
||||
"no-shadow": [2, {"hoist": "functions"}],
|
||||
"quotes": [0, "single"],
|
||||
"linebreak-style": [2, "unix"],
|
||||
|
@ -74,6 +74,7 @@ def delete_selected(modeladmin, request, queryset):
|
||||
protected=protected,
|
||||
opts=opts,
|
||||
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
|
||||
media=modeladmin.media,
|
||||
)
|
||||
|
||||
request.current_app = modeladmin.admin_site.name
|
||||
|
@ -1,5 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import warnings
|
||||
|
||||
from django import forms
|
||||
@ -18,7 +19,7 @@ from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_text, smart_text
|
||||
from django.utils.html import conditional_escape, format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
ACTION_CHECKBOX_NAME = '_selected_action'
|
||||
|
||||
@ -276,6 +277,19 @@ class InlineAdminFormSet(object):
|
||||
'help_text': form_field.help_text,
|
||||
}
|
||||
|
||||
def inline_formset_data(self):
|
||||
verbose_name = self.opts.verbose_name
|
||||
return json.dumps({
|
||||
'name': '#%s' % self.formset.prefix,
|
||||
'options': {
|
||||
'prefix': self.formset.prefix,
|
||||
'addText': ugettext('Add another %(verbose_name)s') % {
|
||||
'verbose_name': capfirst(verbose_name),
|
||||
},
|
||||
'deleteText': ugettext('Remove'),
|
||||
}
|
||||
})
|
||||
|
||||
def _media(self):
|
||||
media = self.opts.media + self.formset.media
|
||||
for fs in self:
|
||||
|
@ -1,6 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import json
|
||||
import operator
|
||||
from collections import OrderedDict
|
||||
from functools import partial, reduce, update_wrapper
|
||||
@ -40,7 +41,7 @@ from django.template.response import SimpleTemplateResponse, TemplateResponse
|
||||
from django.utils import six
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||
from django.utils.html import escape, escapejs, format_html
|
||||
from django.utils.html import escape, format_html
|
||||
from django.utils.http import urlencode, urlquote
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst, get_text_list
|
||||
@ -568,9 +569,9 @@ class ModelAdmin(BaseModelAdmin):
|
||||
extra = '' if settings.DEBUG else '.min'
|
||||
js = [
|
||||
'core.js',
|
||||
'admin/RelatedObjectLookups.js',
|
||||
'vendor/jquery/jquery%s.js' % extra,
|
||||
'jquery.init.js',
|
||||
'admin/RelatedObjectLookups.js',
|
||||
'actions%s.js' % extra,
|
||||
'urlify.js',
|
||||
'prepopulate%s.js' % extra,
|
||||
@ -1084,9 +1085,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||
else:
|
||||
attr = obj._meta.pk.attname
|
||||
value = obj.serializable_value(attr)
|
||||
return SimpleTemplateResponse('admin/popup_response.html', {
|
||||
popup_response_data = json.dumps({
|
||||
'value': value,
|
||||
'obj': obj,
|
||||
'obj': six.text_type(obj),
|
||||
})
|
||||
return SimpleTemplateResponse('admin/popup_response.html', {
|
||||
'popup_response_data': popup_response_data,
|
||||
})
|
||||
|
||||
elif "_continue" in request.POST:
|
||||
@ -1132,11 +1136,14 @@ class ModelAdmin(BaseModelAdmin):
|
||||
# Retrieve the `object_id` from the resolved pattern arguments.
|
||||
value = request.resolver_match.args[0]
|
||||
new_value = obj.serializable_value(attr)
|
||||
return SimpleTemplateResponse('admin/popup_response.html', {
|
||||
popup_response_data = json.dumps({
|
||||
'action': 'change',
|
||||
'value': escape(value),
|
||||
'obj': escapejs(obj),
|
||||
'new_value': escape(new_value),
|
||||
'value': value,
|
||||
'obj': six.text_type(obj),
|
||||
'new_value': new_value,
|
||||
})
|
||||
return SimpleTemplateResponse('admin/popup_response.html', {
|
||||
'popup_response_data': popup_response_data,
|
||||
})
|
||||
|
||||
opts = self.model._meta
|
||||
@ -1300,9 +1307,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||
opts = self.model._meta
|
||||
|
||||
if IS_POPUP_VAR in request.POST:
|
||||
return SimpleTemplateResponse('admin/popup_response.html', {
|
||||
popup_response_data = json.dumps({
|
||||
'action': 'delete',
|
||||
'value': escape(obj_id),
|
||||
'value': obj_id,
|
||||
})
|
||||
return SimpleTemplateResponse('admin/popup_response.html', {
|
||||
'popup_response_data': popup_response_data,
|
||||
})
|
||||
|
||||
self.message_user(request,
|
||||
@ -1332,6 +1342,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||
context.update(
|
||||
to_field_var=TO_FIELD_VAR,
|
||||
is_popup_var=IS_POPUP_VAR,
|
||||
media=self.media,
|
||||
)
|
||||
|
||||
return TemplateResponse(request,
|
||||
|
@ -75,15 +75,15 @@ Requires core.js, SelectBox.js and addevent.js.
|
||||
filter_input.id = field_id + '_input';
|
||||
|
||||
selector_available.appendChild(from_box);
|
||||
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');
|
||||
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');
|
||||
choose_all.className = 'selector-chooseall';
|
||||
|
||||
// <ul class="selector-chooser">
|
||||
var selector_chooser = quickElement('ul', selector_div);
|
||||
selector_chooser.className = 'selector-chooser';
|
||||
var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript:void(0);', 'id', field_id + '_add_link');
|
||||
var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', '#', 'id', field_id + '_add_link');
|
||||
add_link.className = 'selector-add';
|
||||
var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript:void(0);', 'id', field_id + '_remove_link');
|
||||
var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', '#', 'id', field_id + '_remove_link');
|
||||
remove_link.className = 'selector-remove';
|
||||
|
||||
// <div class="selector-chosen">
|
||||
@ -105,7 +105,7 @@ Requires core.js, SelectBox.js and addevent.js.
|
||||
|
||||
var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
|
||||
to_box.className = 'filtered';
|
||||
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');
|
||||
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');
|
||||
clear_all.className = 'selector-clearall';
|
||||
|
||||
from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
|
||||
@ -195,4 +195,12 @@ Requires core.js, SelectBox.js and addevent.js.
|
||||
}
|
||||
};
|
||||
|
||||
addEvent(window, 'load', function(e) {
|
||||
$('select.selectfilter, select.selectfilterstacked').each(function() {
|
||||
var $el = $(this),
|
||||
data = $el.data();
|
||||
SelectFilter.init($el.attr('id'), data.fieldName, parseInt(data.isStacked, 10));
|
||||
});
|
||||
});
|
||||
|
||||
})(django.jQuery);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*global _actions_icnt, gettext, interpolate, ngettext*/
|
||||
/*global gettext, interpolate, ngettext*/
|
||||
(function($) {
|
||||
'use strict';
|
||||
var lastChecked;
|
||||
@ -41,12 +41,13 @@
|
||||
},
|
||||
updateCounter = function() {
|
||||
var sel = $(actionCheckboxes).filter(":checked").length;
|
||||
// _actions_icnt is defined in the generated HTML
|
||||
// data-actions-icnt is defined in the generated HTML
|
||||
// and contains the total amount of objects in the queryset
|
||||
var actions_icnt = $('.action-counter').data('actionsIcnt');
|
||||
$(options.counterContainer).html(interpolate(
|
||||
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
|
||||
sel: sel,
|
||||
cnt: _actions_icnt
|
||||
cnt: actions_icnt
|
||||
}, true));
|
||||
$(options.allToggle).prop("checked", function() {
|
||||
var value;
|
||||
@ -143,4 +144,10 @@
|
||||
allToggle: "#action-toggle",
|
||||
selectedClass: "selected"
|
||||
};
|
||||
$(document).ready(function() {
|
||||
var $actionsEls = $('tr input.action-select');
|
||||
if ($actionsEls.length > 0) {
|
||||
$actionsEls.actions();
|
||||
}
|
||||
});
|
||||
})(django.jQuery);
|
||||
|
@ -1,6 +1,6 @@
|
||||
(function(a){var f;a.fn.actions=function(q){var b=a.extend({},a.fn.actions.defaults,q),g=a(this),e=!1,k=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},l=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},m=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},n=function(){m();
|
||||
a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},p=function(c){c?k():m();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).prop("checked",function(){var a;c===g.length?(a=!0,k()):(a=!1,n());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()&&l()});a(b.allToggle).show().click(function(){p(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);l()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);n();p(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&&a.data(f)!==a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,
|
||||
d.checked);a(g).each(function(){if(a.data(this)===a.data(f)||a.data(this)===a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(a){if(e)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"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're 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"}})(django.jQuery);
|
||||
(function(a){var f;a.fn.actions=function(e){var b=a.extend({},a.fn.actions.defaults,e),g=a(this),k=!1,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(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).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(){var a;c===g.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().click(function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&&
|
||||
a.data(f)!==a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(g).each(function(){if(a.data(this)===a.data(f)||a.data(this)===a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){k=!0});a('form#changelist-form button[name="index"]').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"]').click(function(c){var 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't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're 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(){var e=a("tr input.action-select");0<e.length&&e.actions()})})(django.jQuery);
|
||||
|
@ -105,11 +105,22 @@
|
||||
shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
|
||||
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
|
||||
var now_link = document.createElement('a');
|
||||
now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", -1);");
|
||||
now_link.setAttribute('href', "#");
|
||||
now_link.appendChild(document.createTextNode(gettext('Now')));
|
||||
addEvent(now_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleClockQuicklink(num, -1);
|
||||
});
|
||||
var clock_link = document.createElement('a');
|
||||
clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
|
||||
clock_link.setAttribute('href', '#');
|
||||
clock_link.id = DateTimeShortcuts.clockLinkName + num;
|
||||
addEvent(clock_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
// avoid triggering the document click handler to dismiss the clock
|
||||
e.stopPropagation();
|
||||
DateTimeShortcuts.openClock(num);
|
||||
});
|
||||
|
||||
quickElement(
|
||||
'span', clock_link, '',
|
||||
'class', 'clock-icon',
|
||||
@ -146,15 +157,40 @@
|
||||
quickElement('h2', clock_box, gettext('Choose a time'));
|
||||
var time_list = quickElement('ul', clock_box);
|
||||
time_list.className = 'timelist';
|
||||
quickElement("a", quickElement("li", time_list), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", -1);");
|
||||
quickElement("a", quickElement("li", time_list), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 0);");
|
||||
quickElement("a", quickElement("li", time_list), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 6);");
|
||||
quickElement("a", quickElement("li", time_list), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 12);");
|
||||
quickElement("a", quickElement("li", time_list), gettext("6 p.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 18);");
|
||||
var time_link = quickElement("a", quickElement("li", time_list), gettext("Now"), "href", "#");
|
||||
addEvent(time_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleClockQuicklink(num, -1);
|
||||
});
|
||||
time_link = quickElement("a", quickElement("li", time_list), gettext("Midnight"), "href", "#");
|
||||
addEvent(time_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleClockQuicklink(num, 0);
|
||||
});
|
||||
time_link = quickElement("a", quickElement("li", time_list), gettext("6 a.m."), "href", "#");
|
||||
addEvent(time_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleClockQuicklink(num, 6);
|
||||
});
|
||||
time_link = quickElement("a", quickElement("li", time_list), gettext("Noon"), "href", "#");
|
||||
addEvent(time_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleClockQuicklink(num, 12);
|
||||
});
|
||||
time_link = quickElement("a", quickElement("li", time_list), gettext("6 p.m."), "href", "#");
|
||||
addEvent(time_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleClockQuicklink(num, 18);
|
||||
});
|
||||
|
||||
var cancel_p = quickElement('p', clock_box);
|
||||
cancel_p.className = 'calendar-cancel';
|
||||
quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');');
|
||||
var cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#');
|
||||
addEvent(cancel_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.dismissClock(num);
|
||||
});
|
||||
|
||||
django.jQuery(document).bind('keyup', function(event) {
|
||||
if (event.which === 27) {
|
||||
// ESC key closes popup
|
||||
@ -213,11 +249,21 @@
|
||||
shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
|
||||
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
|
||||
var today_link = document.createElement('a');
|
||||
today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
|
||||
today_link.setAttribute('href', '#');
|
||||
today_link.appendChild(document.createTextNode(gettext('Today')));
|
||||
addEvent(today_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleCalendarQuickLink(num, 0);
|
||||
});
|
||||
var cal_link = document.createElement('a');
|
||||
cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');');
|
||||
cal_link.setAttribute('href', '#');
|
||||
cal_link.id = DateTimeShortcuts.calendarLinkName + num;
|
||||
addEvent(cal_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
// avoid triggering the document click handler to dismiss the calendar
|
||||
e.stopPropagation();
|
||||
DateTimeShortcuts.openCalendar(num);
|
||||
});
|
||||
quickElement(
|
||||
'span', cal_link, '',
|
||||
'class', 'date-icon',
|
||||
@ -255,10 +301,19 @@
|
||||
|
||||
// next-prev links
|
||||
var cal_nav = quickElement('div', cal_box);
|
||||
var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev(' + num + ');');
|
||||
var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', '#');
|
||||
cal_nav_prev.className = 'calendarnav-previous';
|
||||
var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext(' + num + ');');
|
||||
addEvent(cal_nav_prev, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.drawPrev(num);
|
||||
});
|
||||
|
||||
var cal_nav_next = quickElement('a', cal_nav, '>', 'href', '#');
|
||||
cal_nav_next.className = 'calendarnav-next';
|
||||
addEvent(cal_nav_next, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.drawNext(num);
|
||||
});
|
||||
|
||||
// main box
|
||||
var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num);
|
||||
@ -269,16 +324,32 @@
|
||||
// calendar shortcuts
|
||||
var shortcuts = quickElement('div', cal_box);
|
||||
shortcuts.className = 'calendar-shortcuts';
|
||||
quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);');
|
||||
var day_link = quickElement('a', shortcuts, gettext('Yesterday'), 'href', '#');
|
||||
addEvent(day_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleCalendarQuickLink(num, -1);
|
||||
});
|
||||
shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0'));
|
||||
quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
|
||||
day_link = quickElement('a', shortcuts, gettext('Today'), 'href', '#');
|
||||
addEvent(day_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleCalendarQuickLink(num, 0);
|
||||
});
|
||||
shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0'));
|
||||
quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);');
|
||||
day_link = quickElement('a', shortcuts, gettext('Tomorrow'), 'href', '#');
|
||||
addEvent(day_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.handleCalendarQuickLink(num, +1);
|
||||
});
|
||||
|
||||
// cancel bar
|
||||
var cancel_p = quickElement('p', cal_box);
|
||||
cancel_p.className = 'calendar-cancel';
|
||||
quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');');
|
||||
var cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#');
|
||||
addEvent(cancel_link, 'click', function(e) {
|
||||
e.preventDefault();
|
||||
DateTimeShortcuts.dismissCalendar(num);
|
||||
});
|
||||
django.jQuery(document).bind('keyup', function(event) {
|
||||
if (event.which === 27) {
|
||||
// ESC key closes popup
|
||||
@ -340,15 +411,11 @@
|
||||
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,
|
||||
"');DateTimeShortcuts.calendarInputs[",
|
||||
num,
|
||||
"].focus();document.getElementById(DateTimeShortcuts.calendarDivName1+",
|
||||
num,
|
||||
").style.display='none';}"].join('');
|
||||
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';
|
||||
};
|
||||
},
|
||||
handleCalendarQuickLink: function(num, offset) {
|
||||
var d = DateTimeShortcuts.now();
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Handles related-objects functionality: lookup link for raw_id_fields
|
||||
// and Add Another links.
|
||||
|
||||
(function() {
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
function html_unescape(text) {
|
||||
@ -157,4 +157,11 @@
|
||||
window.showAddAnotherPopup = showRelatedObjectPopup;
|
||||
window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
|
||||
|
||||
})();
|
||||
$(document).ready(function() {
|
||||
$("a[data-popup-opener]").click(function(event) {
|
||||
event.preventDefault();
|
||||
opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener"));
|
||||
});
|
||||
});
|
||||
|
||||
})(django.jQuery);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*global gettext, pgettext, get_format, quickElement, removeChildren*/
|
||||
/*global gettext, pgettext, get_format, quickElement, removeChildren, addEvent*/
|
||||
/*
|
||||
calendar.js - Calendar functions by Adrian Holovaty
|
||||
depends on core.js for utility functions like removeChildren or quickElement
|
||||
@ -100,6 +100,14 @@ depends on core.js for utility functions like removeChildren or quickElement
|
||||
nonDayCell.className = "nonday";
|
||||
}
|
||||
|
||||
function calendarMonth(y, m) {
|
||||
function onClick(e) {
|
||||
e.preventDefault();
|
||||
callback(y, m, django.jQuery(this).text());
|
||||
}
|
||||
return onClick;
|
||||
}
|
||||
|
||||
// Draw days of month
|
||||
var currentDay = 1;
|
||||
for (i = startingPos; currentDay <= days; i++) {
|
||||
@ -121,8 +129,8 @@ depends on core.js for utility functions like removeChildren or quickElement
|
||||
}
|
||||
|
||||
var cell = quickElement('td', tableRow, '', 'class', todayClass);
|
||||
|
||||
quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '(' + year + ',' + month + ',' + currentDay + '));');
|
||||
var link = quickElement('a', cell, currentDay, 'href', '#');
|
||||
addEvent(link, 'click', calendarMonth(year, month));
|
||||
currentDay++;
|
||||
}
|
||||
|
||||
|
9
django/contrib/admin/static/admin/js/cancel.js
Normal file
9
django/contrib/admin/static/admin/js/cancel.js
Normal file
@ -0,0 +1,9 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
$(function() {
|
||||
$('.cancel-link').click(function(e) {
|
||||
e.preventDefault();
|
||||
window.history.back();
|
||||
});
|
||||
});
|
||||
})(django.jQuery);
|
46
django/contrib/admin/static/admin/js/change_form.js
Normal file
46
django/contrib/admin/static/admin/js/change_form.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*global showAddAnotherPopup, showRelatedObjectLookupPopup showRelatedObjectPopup updateRelatedObjectLinks*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
$(document).ready(function() {
|
||||
var modelName = $('#django-admin-form-add-constants').data('modelName');
|
||||
$('.add-another').click(function(e) {
|
||||
e.preventDefault();
|
||||
var event = $.Event('django:add-another-related');
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
showAddAnotherPopup(this);
|
||||
}
|
||||
});
|
||||
$('.related-lookup').click(function(e) {
|
||||
e.preventDefault();
|
||||
var event = $.Event('django:lookup-related');
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
showRelatedObjectLookupPopup(this);
|
||||
}
|
||||
});
|
||||
$('body').on('click', '.related-widget-wrapper-link', function(e) {
|
||||
e.preventDefault();
|
||||
if (this.href) {
|
||||
var event = $.Event('django:show-related', {href: this.href});
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
showRelatedObjectPopup(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
$('body').on('change', '.related-widget-wrapper select', function(e) {
|
||||
var event = $.Event('django:update-related');
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
updateRelatedObjectLinks(this);
|
||||
}
|
||||
});
|
||||
$('.related-widget-wrapper select').trigger('change');
|
||||
|
||||
if (modelName) {
|
||||
$('form#' + modelName + '_form :input:visible:enabled:first').focus();
|
||||
}
|
||||
});
|
||||
})(django.jQuery);
|
@ -49,11 +49,11 @@
|
||||
// If forms are laid out as table rows, insert the
|
||||
// "add" button in a new table row:
|
||||
var numCols = this.eq(-1).children().length;
|
||||
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
|
||||
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="#">' + options.addText + "</a></tr>");
|
||||
addButton = $parent.find("tr:last a");
|
||||
} else {
|
||||
// Otherwise, insert it immediately after the last form:
|
||||
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
|
||||
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="#">' + options.addText + "</a></div>");
|
||||
addButton = $this.filter(":last").next().find("a");
|
||||
}
|
||||
addButton.click(function(e) {
|
||||
@ -66,15 +66,15 @@
|
||||
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('<div><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></div>");
|
||||
row.children(":last").append('<div><a class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></div>");
|
||||
} else if (row.is("ul") || row.is("ol")) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></li>");
|
||||
row.append('<li><a class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></li>");
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// last child element of the form's container:
|
||||
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
|
||||
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></span>");
|
||||
}
|
||||
row.find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, totalForms.val());
|
||||
@ -272,4 +272,19 @@
|
||||
|
||||
return $rows;
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
$(".js-inline-admin-formset").each(function() {
|
||||
var data = $(this).data(),
|
||||
inlineOptions = data.inlineFormset;
|
||||
switch(data.inlineType) {
|
||||
case "stacked":
|
||||
$(inlineOptions.name + "-group .inline-related").stackedFormset(inlineOptions.options);
|
||||
break;
|
||||
case "tabular":
|
||||
$(inlineOptions.name + "-group .tabular.inline-related tbody tr").tabularFormset(inlineOptions.options);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
})(django.jQuery);
|
||||
|
@ -1,9 +1,10 @@
|
||||
(function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),e=b(this);d=e.parent();var k=function(a,f,l){var c=new RegExp("("+f+"-(\\d+|__prefix__))");f=f+"-"+l;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(c,f));a.id&&(a.id=a.id.replace(c,f));a.name&&(a.name=a.name.replace(c,f))},h=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),l=parseInt(h.val(),10),f=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),c=""===f.val()||0<f.val()-h.val();
|
||||
e.each(function(f){b(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});if(e.length&&c){var m;"TR"===e.prop("tagName")?(e=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+e+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),m=d.find("tr:last a")):(e.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),m=e.filter(":last").next().find("a"));m.click(function(c){c.preventDefault();c=b("#"+a.prefix+
|
||||
"-empty");var g=c.clone(!0);g.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);g.is("tr")?g.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):g.is("ul")||g.is("ol")?g.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):g.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");g.find("*").each(function(){k(this,
|
||||
a.prefix,h.val())});g.insertBefore(b(c));b(h).val(parseInt(h.val(),10)+1);l+=1;""!==f.val()&&0>=f.val()-h.val()&&m.parent().hide();g.find("a."+a.deleteCssClass).click(function(c){c.preventDefault();g.remove();--l;a.removed&&a.removed(g);b(document).trigger("formset:removed",[g,a.prefix]);c=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(c.length);(""===f.val()||0<f.val()-c.length)&&m.parent().show();var d,e,h=function(){k(this,a.prefix,d)};d=0;for(e=c.length;d<e;d++)k(b(c).get(d),a.prefix,
|
||||
d),b(c.get(d)).find("*").each(h)});a.added&&a.added(g);b(document).trigger("formset:added",[g,a.prefix])})}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};b.fn.tabularFormset=function(d){var a=b(this),e=function(l){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")},
|
||||
k=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var f=b(this).find("input, select, textarea"),c=f.data("dependency_list")||[],d=[];b.each(c,function(b,c){d.push("#"+a.find(".field-"+c).find("input, select, textarea").attr("id"))});
|
||||
d.length&&f.prepopulate(d,f.attr("maxlength"))})};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:e,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();e(a)}});return a};b.fn.stackedFormset=function(d){var a=b(this),e=function(d){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,
|
||||
"#"+a))})},k=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(b,c){e.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))});
|
||||
e.length&&d.prepopulate(e,d.attr("maxlength"))})};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:e,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();e(a)}});return a}})(django.jQuery);
|
||||
(function(c){c.fn.formset=function(b){var a=c.extend({},c.fn.formset.defaults,b),d=c(this);b=d.parent();var k=function(a,g,l){var b=new RegExp("("+g+"-(\\d+|__prefix__))");g=g+"-"+l;c(a).prop("for")&&c(a).prop("for",c(a).prop("for").replace(b,g));a.id&&(a.id=a.id.replace(b,g));a.name&&(a.name=a.name.replace(b,g))},e=c("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),l=parseInt(e.val(),10),g=c("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),h=""===g.val()||0<g.val()-e.val();
|
||||
d.each(function(g){c(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});if(d.length&&h){var m;"TR"===d.prop("tagName")?(d=this.eq(-1).children().length,b.append('<tr class="'+a.addCssClass+'"><td colspan="'+d+'"><a href="#">'+a.addText+"</a></tr>"),m=b.find("tr:last a")):(d.filter(":last").after('<div class="'+a.addCssClass+'"><a href="#">'+a.addText+"</a></div>"),m=d.filter(":last").next().find("a"));m.click(function(b){b.preventDefault();b=c("#"+a.prefix+"-empty");var f=b.clone(!0);f.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",
|
||||
a.prefix+"-"+l);f.is("tr")?f.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></div>"):f.is("ul")||f.is("ol")?f.append('<li><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></li>"):f.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></span>");f.find("*").each(function(){k(this,a.prefix,e.val())});f.insertBefore(c(b));c(e).val(parseInt(e.val(),10)+1);l+=1;""!==g.val()&&0>=g.val()-e.val()&&m.parent().hide();
|
||||
f.find("a."+a.deleteCssClass).click(function(b){b.preventDefault();f.remove();--l;a.removed&&a.removed(f);c(document).trigger("formset:removed",[f,a.prefix]);b=c("."+a.formCssClass);c("#id_"+a.prefix+"-TOTAL_FORMS").val(b.length);(""===g.val()||0<g.val()-b.length)&&m.parent().show();var h,d,e=function(){k(this,a.prefix,h)};h=0;for(d=b.length;h<d;h++)k(c(b).get(h),a.prefix,h),c(b.get(h)).find("*").each(e)});a.added&&a.added(f);c(document).trigger("formset:added",[f,a.prefix])})}return this};c.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};c.fn.tabularFormset=function(b){var a=c(this),d=function(b){c(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")},k=function(){"undefined"!==typeof SelectFilter&&(c(".selectfilter").each(function(a,c){var b=c.name.split("-");SelectFilter.init(c.id,
|
||||
b[b.length-1],!1)}),c(".selectfilterstacked").each(function(a,c){var b=c.name.split("-");SelectFilter.init(c.id,b[b.length-1],!0)}))},e=function(a){a.find(".prepopulated_field").each(function(){var b=c(this).find("input, select, textarea"),h=b.data("dependency_list")||[],d=[];c.each(h,function(c,b){d.push("#"+a.find(".field-"+b).find("input, select, textarea").attr("id"))});d.length&&b.prepopulate(d,b.attr("maxlength"))})};a.formset({prefix:b.prefix,addText:b.addText,formCssClass:"dynamic-"+b.prefix,
|
||||
deleteCssClass:"inline-deletelink",deleteText:b.deleteText,emptyCssClass:"empty-form",removed:d,added:function(a){e(a);"undefined"!==typeof DateTimeShortcuts&&(c(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();d(a)}});return a};c.fn.stackedFormset=function(b){var a=c(this),d=function(b){c(a.selector).find(".inline_label").each(function(a){a+=1;c(this).html(c(this).html().replace(/(#\d+)/g,"#"+a))})},k=function(){"undefined"!==typeof SelectFilter&&(c(".selectfilter").each(function(a,c){var b=
|
||||
c.name.split("-");SelectFilter.init(c.id,b[b.length-1],!1)}),c(".selectfilterstacked").each(function(a,c){var b=c.name.split("-");SelectFilter.init(c.id,b[b.length-1],!0)}))},e=function(a){a.find(".prepopulated_field").each(function(){var b=c(this).find("input, select, textarea"),d=b.data("dependency_list")||[],e=[];c.each(d,function(b,c){e.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))});e.length&&b.prepopulate(e,b.attr("maxlength"))})};a.formset({prefix:b.prefix,
|
||||
addText:b.addText,formCssClass:"dynamic-"+b.prefix,deleteCssClass:"inline-deletelink",deleteText:b.deleteText,emptyCssClass:"empty-form",removed:d,added:function(a){e(a);"undefined"!==typeof DateTimeShortcuts&&(c(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();d(a)}});return a};c(document).ready(function(){c(".js-inline-admin-formset").each(function(){var b=c(this).data(),a=b.inlineFormset;switch(b.inlineType){case "stacked":c(a.name+"-group .inline-related").stackedFormset(a.options);
|
||||
break;case "tabular":c(a.name+"-group .tabular.inline-related tbody tr").tabularFormset(a.options)}})})})(django.jQuery);
|
||||
|
16
django/contrib/admin/static/admin/js/popup_response.js
Normal file
16
django/contrib/admin/static/admin/js/popup_response.js
Normal file
@ -0,0 +1,16 @@
|
||||
/*global opener */
|
||||
(function() {
|
||||
'use strict';
|
||||
var initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);
|
||||
switch(initData.action) {
|
||||
case 'change':
|
||||
opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value);
|
||||
break;
|
||||
case 'delete':
|
||||
opener.dismissDeleteRelatedObjectPopup(window, initData.value);
|
||||
break;
|
||||
default:
|
||||
opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj);
|
||||
break;
|
||||
}
|
||||
})();
|
10
django/contrib/admin/static/admin/js/prepopulate_init.js
Normal file
10
django/contrib/admin/static/admin/js/prepopulate_init.js
Normal file
@ -0,0 +1,10 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
var fields = $('#django-admin-prepopulated-fields-constants').data('prepopulatedFields');
|
||||
$.each(fields, function(index, field) {
|
||||
$('.empty-form .form-row .field-' + field.name + ', .empty-form.form-row .field-' + field.name).addClass('prepopulated_field');
|
||||
$(field.id).data('dependency_list', field.dependency_list).prepopulate(
|
||||
field.dependency_ids, field.maxLength, field.allowUnicode
|
||||
);
|
||||
});
|
||||
})(django.jQuery);
|
@ -3,14 +3,13 @@
|
||||
{% for field in action_form %}{% if field.label %}<label>{{ field.label }} {% endif %}{{ field }}{% if field.label %}</label>{% endif %}{% endfor %}
|
||||
<button type="submit" class="button" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button>
|
||||
{% if actions_selection_counter %}
|
||||
<script type="text/javascript">var _actions_icnt="{{ cl.result_list|length|default:"0" }}";</script>
|
||||
<span class="action-counter">{{ selection_note }}</span>
|
||||
<span class="action-counter" data-actions-icnt="{{ cl.result_list|length }}">{{ selection_note }}</span>
|
||||
{% if cl.result_count != cl.result_list|length %}
|
||||
<span class="all">{{ selection_note_all }}</span>
|
||||
<span class="question">
|
||||
<a href="javascript:;" title="{% trans "Click here to select the objects across all pages" %}">{% blocktrans with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktrans %}</a>
|
||||
<a href="#" title="{% trans "Click here to select the objects across all pages" %}">{% blocktrans with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktrans %}</a>
|
||||
</span>
|
||||
<span class="clear"><a href="javascript:;">{% trans "Clear selection" %}</a></span>
|
||||
<span class="clear"><a href="#">{% trans "Clear selection" %}</a></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -8,7 +8,3 @@
|
||||
<p>{% trans "Enter a username and password." %}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block after_field_sets %}
|
||||
<script type="text/javascript">document.getElementById("id_username").focus();</script>
|
||||
{% endblock %}
|
||||
|
@ -54,7 +54,6 @@
|
||||
<input type="submit" value="{% trans 'Change password' %}" class="default" />
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">document.getElementById("id_password1").focus();</script>
|
||||
</div>
|
||||
</form></div>
|
||||
{% endblock %}
|
||||
|
@ -68,49 +68,12 @@
|
||||
{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
|
||||
|
||||
{% block admin_change_form_document_ready %}
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
$(document).ready(function() {
|
||||
$('.add-another').click(function(e) {
|
||||
e.preventDefault();
|
||||
var event = $.Event('django:add-another-related');
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
showAddAnotherPopup(this);
|
||||
}
|
||||
});
|
||||
$('.related-lookup').click(function(e) {
|
||||
e.preventDefault();
|
||||
var event = $.Event('django:lookup-related');
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
showRelatedObjectLookupPopup(this);
|
||||
}
|
||||
});
|
||||
$('body').on('click', '.related-widget-wrapper-link', function(e) {
|
||||
e.preventDefault();
|
||||
if (this.href) {
|
||||
var event = $.Event('django:show-related', {href: this.href});
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
showRelatedObjectPopup(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
$('body').on('change', '.related-widget-wrapper select', function(e) {
|
||||
var event = $.Event('django:update-related');
|
||||
$(this).trigger(event);
|
||||
if (!event.isDefaultPrevented()) {
|
||||
updateRelatedObjectLinks(this);
|
||||
}
|
||||
});
|
||||
$('.related-widget-wrapper select').trigger('change');
|
||||
|
||||
<script type="text/javascript"
|
||||
id="django-admin-form-add-constants"
|
||||
src="{% static 'admin/js/change_form.js' %}"
|
||||
{% if adminform and add %}
|
||||
$('form#{{ opts.model_name }}_form :input:visible:enabled:first').focus()
|
||||
{% endif %}
|
||||
});
|
||||
})(django.jQuery);
|
||||
data-model-name="{{ opts.model_name }}"
|
||||
{% endif %}>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -21,15 +21,6 @@
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{{ media.js }}
|
||||
{% if action_form %}{% if actions_on_top or actions_on_bottom %}
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
$(document).ready(function($) {
|
||||
$("tr input.action-select").actions();
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
{% endif %}{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
|
||||
|
@ -1,5 +1,10 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n admin_urls %}
|
||||
{% load i18n admin_urls admin_static %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ media }}
|
||||
<script type="text/javascript" src="{% static 'admin/js/cancel.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation{% endblock %}
|
||||
|
||||
@ -39,7 +44,7 @@
|
||||
{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1" />{% endif %}
|
||||
{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}" />{% endif %}
|
||||
<input type="submit" value="{% trans "Yes, I'm sure" %}" />
|
||||
<a href="#" onclick="window.history.back(); return false;" class="button cancel-link">{% trans "No, take me back" %}</a>
|
||||
<a href="#" class="button cancel-link">{% trans "No, take me back" %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
@ -1,5 +1,10 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n l10n admin_urls %}
|
||||
{% load i18n l10n admin_urls admin_static %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ media }}
|
||||
<script type="text/javascript" src="{% static 'admin/js/cancel.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
|
||||
|
||||
@ -42,7 +47,7 @@
|
||||
<input type="hidden" name="action" value="delete_selected" />
|
||||
<input type="hidden" name="post" value="yes" />
|
||||
<input type="submit" value="{% trans "Yes, I'm sure" %}" />
|
||||
<a href="#" onclick="window.history.back(); return false;" class="button cancel-link">{% trans "No, take me back" %}</a>
|
||||
<a href="#" class="button cancel-link">{% trans "No, take me back" %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
@ -1,5 +1,8 @@
|
||||
{% load i18n admin_urls admin_static %}
|
||||
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
||||
<div class="js-inline-admin-formset inline-group"
|
||||
id="{{ inline_admin_formset.formset.prefix }}-group"
|
||||
data-inline-type="stacked"
|
||||
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
|
||||
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
|
||||
{{ inline_admin_formset.formset.management_form }}
|
||||
{{ inline_admin_formset.formset.non_form_errors }}
|
||||
@ -18,13 +21,3 @@
|
||||
{{ inline_admin_form.fk_field.field }}
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
$("#{{ inline_admin_formset.formset.prefix|escapejs }}-group .inline-related").stackedFormset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix|escapejs }}",
|
||||
deleteText: "{% filter escapejs %}{% trans "Remove" %}{% endfilter %}",
|
||||
addText: "{% filter escapejs %}{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|capfirst %}Add another {{ verbose_name }}{% endblocktrans %}{% endfilter %}"
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
@ -1,5 +1,7 @@
|
||||
{% load i18n admin_urls admin_static admin_modify %}
|
||||
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
||||
<div class="js-inline-admin-formset inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"
|
||||
data-inline-type="tabular"
|
||||
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
|
||||
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
|
||||
{{ inline_admin_formset.formset.management_form }}
|
||||
<fieldset class="module">
|
||||
@ -71,14 +73,3 @@
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
(function($) {
|
||||
$("#{{ inline_admin_formset.formset.prefix|escapejs }}-group .tabular.inline-related tbody tr").tabularFormset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix|escapejs }}",
|
||||
addText: "{% filter escapejs %}{% blocktrans with inline_admin_formset.opts.verbose_name|capfirst as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}{% endfilter %}",
|
||||
deleteText: "{% filter escapejs %}{% trans 'Remove' %}{% endfilter %}"
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
@ -62,8 +62,5 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById('id_username').focus()
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,15 +1,11 @@
|
||||
{% load i18n %}<!DOCTYPE html>
|
||||
{% load i18n admin_static %}<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>{% trans 'Popup closing...' %}</title></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
{% if action == 'change' %}
|
||||
opener.dismissChangeRelatedObjectPopup(window, "{{ value|escapejs }}", "{{ obj|escapejs }}", "{{ new_value|escapejs }}");
|
||||
{% elif action == 'delete' %}
|
||||
opener.dismissDeleteRelatedObjectPopup(window, "{{ value|escapejs }}");
|
||||
{% else %}
|
||||
opener.dismissAddRelatedObjectPopup(window, "{{ value|escapejs }}", "{{ obj|escapejs }}");
|
||||
{% endif %}
|
||||
<script type="text/javascript"
|
||||
id="django-admin-popup-response-constants"
|
||||
src="{% static "admin/js/popup_response.js" %}"
|
||||
data-popup-response="{{ popup_response_data }}">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,28 +1,6 @@
|
||||
{% load l10n %}
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
var field;
|
||||
|
||||
{% for field in prepopulated_fields %}
|
||||
field = {
|
||||
id: '#{{ field.field.auto_id }}',
|
||||
dependency_ids: [],
|
||||
dependency_list: [],
|
||||
maxLength: {{ field.field.field.max_length|default:"50"|unlocalize }},
|
||||
allowUnicode: {{ field.field.field.allow_unicode|default:"false"|lower }}
|
||||
};
|
||||
|
||||
{% for dependency in field.dependencies %}
|
||||
field['dependency_ids'].push('#{{ dependency.auto_id }}');
|
||||
field['dependency_list'].push('{{ dependency.name }}');
|
||||
{% endfor %}
|
||||
|
||||
{% comment %}
|
||||
Mark prepopulated fields in the main form and stacked inlines (.empty-form .form-row) and in tabular inlines (.empty-form.form-row)
|
||||
{% endcomment %}
|
||||
$('.empty-form .form-row .field-{{ field.field.name }}, .empty-form.form-row .field-{{ field.field.name }}').addClass('prepopulated_field');
|
||||
$(field.id).data('dependency_list', field['dependency_list'])
|
||||
.prepopulate(field['dependency_ids'], field.maxLength, field.allowUnicode);
|
||||
{% endfor %}
|
||||
})(django.jQuery);
|
||||
{% load l10n admin_static %}
|
||||
<script type="text/javascript"
|
||||
id="django-admin-prepopulated-fields-constants"
|
||||
src="{% static "admin/js/prepopulate_init.js" %}"
|
||||
data-prepopulated-fields="{{ prepopulated_fields_json }}">
|
||||
</script>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div id="toolbar"><form id="changelist-search" method="get">
|
||||
<div><!-- DIV needed for valid HTML -->
|
||||
<label for="searchbar"><img src="{% static "admin/img/search.svg" %}" alt="Search" /></label>
|
||||
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" />
|
||||
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" autofocus />
|
||||
<input type="submit" value="{% trans 'Search' %}" />
|
||||
{% if show_result_count %}
|
||||
<span class="small quiet">{% blocktrans count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% if cl.show_full_result_count %}{% blocktrans with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktrans %}{% else %}{% trans "Show all" %}{% endif %}</a>)</span>
|
||||
@ -13,5 +13,4 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</form></div>
|
||||
<script type="text/javascript">document.getElementById("searchbar").focus();</script>
|
||||
{% endif %}
|
||||
|
@ -54,7 +54,6 @@
|
||||
<input type="submit" value="{% trans 'Change my password' %}" class="default" />
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">document.getElementById("id_old_password").focus();</script>
|
||||
</div>
|
||||
</form></div>
|
||||
|
||||
|
@ -19,7 +19,7 @@ from django.template.loader import get_template
|
||||
from django.utils import formats
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import escapejs, format_html
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -254,13 +254,11 @@ def items_for_result(cl, result, form):
|
||||
else:
|
||||
attr = pk
|
||||
value = result.serializable_value(attr)
|
||||
result_id = escapejs(value)
|
||||
link_or_text = format_html(
|
||||
'<a href="{}"{}>{}</a>',
|
||||
url,
|
||||
format_html(
|
||||
' onclick="opener.dismissRelatedLookupPopup(window, '
|
||||
''{}'); return false;"', result_id
|
||||
' data-popup-opener="{}"', value
|
||||
) if cl.is_popup else '',
|
||||
result_repr)
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import json
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
@ -17,7 +19,22 @@ def prepopulated_fields_js(context):
|
||||
for inline_admin_form in inline_admin_formset:
|
||||
if inline_admin_form.original is None:
|
||||
prepopulated_fields.extend(inline_admin_form.prepopulated_fields)
|
||||
context.update({'prepopulated_fields': prepopulated_fields})
|
||||
|
||||
prepopulated_fields_json = []
|
||||
for field in prepopulated_fields:
|
||||
prepopulated_fields_json.append({
|
||||
"id": "#%s" % field["field"].auto_id,
|
||||
"name": field["field"].name,
|
||||
"dependency_ids": ["#%s" % dependency.auto_id for dependency in field["dependencies"]],
|
||||
"dependency_list": [dependency.name for dependency in field["dependencies"]],
|
||||
"maxLength": field["field"].field.max_length or 50,
|
||||
"allowUnicode": getattr(field["field"].field, "allow_unicode", False)
|
||||
})
|
||||
|
||||
context.update({
|
||||
'prepopulated_fields': prepopulated_fields,
|
||||
'prepopulated_fields_json': json.dumps(prepopulated_fields_json),
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
|
@ -2,10 +2,21 @@ import os
|
||||
from unittest import SkipTest
|
||||
|
||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||
from django.test import modify_settings
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
class CSPMiddleware(object):
|
||||
"""The admin's JavaScript should be compatible with CSP."""
|
||||
def process_response(self, request, response):
|
||||
response['Content-Security-Policy'] = "default-src 'self'"
|
||||
return response
|
||||
|
||||
|
||||
@modify_settings(
|
||||
MIDDLEWARE_CLASSES={'append': 'django.contrib.admin.tests.CSPMiddleware'},
|
||||
)
|
||||
class AdminSeleniumWebDriverTestCase(StaticLiveServerTestCase):
|
||||
|
||||
available_apps = [
|
||||
|
@ -15,7 +15,7 @@ from django.template.loader import render_to_string
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import (
|
||||
escape, escapejs, format_html, format_html_join, smart_urlquote,
|
||||
escape, format_html, format_html_join, smart_urlquote,
|
||||
)
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import Truncator
|
||||
@ -45,16 +45,11 @@ class FilteredSelectMultiple(forms.SelectMultiple):
|
||||
attrs['class'] = 'selectfilter'
|
||||
if self.is_stacked:
|
||||
attrs['class'] += 'stacked'
|
||||
output = [
|
||||
super(FilteredSelectMultiple, self).render(name, value, attrs, choices),
|
||||
'<script type="text/javascript">addEvent(window, "load", function(e) {',
|
||||
# TODO: "id_" is hard-coded here. This should instead use the
|
||||
# correct API to determine the ID dynamically.
|
||||
'SelectFilter.init("id_%s", "%s", %s); });</script>\n' % (
|
||||
name, escapejs(self.verbose_name), int(self.is_stacked),
|
||||
),
|
||||
]
|
||||
return mark_safe(''.join(output))
|
||||
|
||||
attrs['data-field-name'] = self.verbose_name
|
||||
attrs['data-is-stacked'] = int(self.is_stacked)
|
||||
output = super(FilteredSelectMultiple, self).render(name, value, attrs, choices)
|
||||
return mark_safe(output)
|
||||
|
||||
|
||||
class AdminDateWidget(forms.DateInput):
|
||||
|
@ -78,6 +78,10 @@ class UserCreationForm(forms.ModelForm):
|
||||
model = User
|
||||
fields = ("username",)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UserCreationForm, self).__init__(*args, **kwargs)
|
||||
self.fields['username'].widget.attrs.update({'autofocus': ''})
|
||||
|
||||
def clean_password2(self):
|
||||
password1 = self.cleaned_data.get("password1")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
@ -126,7 +130,10 @@ class AuthenticationForm(forms.Form):
|
||||
Base class for authenticating users. Extend this to get a form that accepts
|
||||
username/password logins.
|
||||
"""
|
||||
username = forms.CharField(max_length=254)
|
||||
username = forms.CharField(
|
||||
max_length=254,
|
||||
widget=forms.TextInput(attrs={'autofocus': ''}),
|
||||
)
|
||||
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
|
||||
|
||||
error_messages = {
|
||||
@ -306,8 +313,10 @@ class PasswordChangeForm(SetPasswordForm):
|
||||
'password_incorrect': _("Your old password was entered incorrectly. "
|
||||
"Please enter it again."),
|
||||
})
|
||||
old_password = forms.CharField(label=_("Old password"),
|
||||
widget=forms.PasswordInput)
|
||||
old_password = forms.CharField(
|
||||
label=_("Old password"),
|
||||
widget=forms.PasswordInput(attrs={'autofocus': ''}),
|
||||
)
|
||||
|
||||
field_order = ['old_password', 'new_password1', 'new_password2']
|
||||
|
||||
@ -334,7 +343,7 @@ class AdminPasswordChangeForm(forms.Form):
|
||||
required_css_class = 'required'
|
||||
password1 = forms.CharField(
|
||||
label=_("Password"),
|
||||
widget=forms.PasswordInput,
|
||||
widget=forms.PasswordInput(attrs={'autofocus': ''}),
|
||||
help_text=password_validation.password_validators_help_text_html(),
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
|
@ -26,10 +26,17 @@ In your custom ``change_form.html`` template, extend the
|
||||
.. code-block:: html+django
|
||||
|
||||
{% extends 'admin/change_form.html' %}
|
||||
{% load admin_static %}
|
||||
|
||||
{% block admin_change_form_document_ready %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
<script type="text/javascript" src="{% static 'app/formset_handlers.js' %}></script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
.. snippet:: javascript
|
||||
:filename: app/static/app/formset_handlers.js
|
||||
|
||||
(function($) {
|
||||
$(document).on('formset:added', function(event, $row, formsetName) {
|
||||
if (formsetName == 'author_set') {
|
||||
@ -41,8 +48,6 @@ In your custom ``change_form.html`` template, extend the
|
||||
// Row removed
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Two points to keep in mind:
|
||||
|
||||
@ -60,16 +65,20 @@ namespace, just listen to the event triggered from there. For example:
|
||||
.. code-block:: html+django
|
||||
|
||||
{% extends 'admin/change_form.html' %}
|
||||
{% load admin_static %}
|
||||
|
||||
{% block admin_change_form_document_ready %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
django.jQuery(document).on('formset:added', function(event, $row, formsetName) {
|
||||
// Row added
|
||||
});
|
||||
|
||||
django.jQuery(document).on('formset:removed', function(event, $row, formsetName) {
|
||||
// Row removed
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" src="{% static 'app/unregistered_handlers.js' %}></script>
|
||||
{% endblock %}
|
||||
|
||||
.. snippet:: javascript
|
||||
:filename: app/static/app/unregistered_handlers.js
|
||||
|
||||
django.jQuery(document).on('formset:added', function(event, $row, formsetName) {
|
||||
// Row added
|
||||
});
|
||||
|
||||
django.jQuery(document).on('formset:removed', function(event, $row, formsetName) {
|
||||
// Row removed
|
||||
});
|
||||
|
@ -39,6 +39,9 @@ Minor features
|
||||
* The success message that appears after adding or editing an object now
|
||||
contains a link to the object's change form.
|
||||
|
||||
* All inline JavaScript is removed so you can enable the
|
||||
``Content-Security-Policy`` HTTP header if you wish.
|
||||
|
||||
:mod:`django.contrib.admindocs`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -74,7 +74,6 @@ class AdminCustomUrlsTest(TestCase):
|
||||
"description": "Description of added action",
|
||||
}
|
||||
response = self.client.post(reverse('admin_custom_urls:admin_custom_urls_action_add'), post_data)
|
||||
self.assertContains(response, 'dismissAddRelatedObjectPopup')
|
||||
self.assertContains(response, 'Action added through a popup')
|
||||
|
||||
def test_admin_URLs_no_clash(self):
|
||||
|
@ -1,5 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
@ -8,14 +10,16 @@ class TestTemplates(SimpleTestCase):
|
||||
def test_javascript_escaping(self):
|
||||
context = {
|
||||
'inline_admin_formset': {
|
||||
'formset': {'prefix': 'my-prefix'},
|
||||
'opts': {'verbose_name': 'verbose name\\'},
|
||||
'inline_formset_data': json.dumps({
|
||||
'formset': {'prefix': 'my-prefix'},
|
||||
'opts': {'verbose_name': 'verbose name\\'},
|
||||
}),
|
||||
},
|
||||
}
|
||||
output = render_to_string('admin/edit_inline/stacked.html', context)
|
||||
self.assertIn('prefix: "my\\u002Dprefix",', output)
|
||||
self.assertIn('addText: "Add another Verbose name\\u005C"', output)
|
||||
self.assertIn('"prefix": "my-prefix"', output)
|
||||
self.assertIn('"verbose_name": "verbose name\\\\"', output)
|
||||
|
||||
output = render_to_string('admin/edit_inline/tabular.html', context)
|
||||
self.assertIn('prefix: "my\\u002Dprefix",', output)
|
||||
self.assertIn('addText: "Add another Verbose name\\u005C"', output)
|
||||
self.assertIn('"prefix": "my-prefix"', output)
|
||||
self.assertIn('"verbose_name": "verbose name\\\\"', output)
|
||||
|
@ -76,7 +76,7 @@ class TestInline(TestDataMixin, TestCase):
|
||||
# The heading for the m2m inline block uses the right text
|
||||
self.assertContains(response, '<h2>Author-book relationships</h2>')
|
||||
# The "add another" label is correct
|
||||
self.assertContains(response, 'Add another Author\\u002Dbook relationship')
|
||||
self.assertContains(response, 'Add another Author-book relationship')
|
||||
# The '+' is dropped from the autogenerated form prefix (Author_books+)
|
||||
self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"')
|
||||
|
||||
@ -133,14 +133,20 @@ class TestInline(TestDataMixin, TestCase):
|
||||
response = self.client.get(reverse('admin:admin_inlines_novel_add'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# View should have the child inlines section
|
||||
self.assertContains(response, '<div class="inline-group" id="chapter_set-group">')
|
||||
self.assertContains(
|
||||
response,
|
||||
'<div class="js-inline-admin-formset inline-group" id="chapter_set-group"'
|
||||
)
|
||||
|
||||
def test_callable_lookup(self):
|
||||
"""Admin inline should invoke local callable when its name is listed in readonly_fields"""
|
||||
response = self.client.get(reverse('admin:admin_inlines_poll_add'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# Add parent object view should have the child inlines section
|
||||
self.assertContains(response, '<div class="inline-group" id="question_set-group">')
|
||||
self.assertContains(
|
||||
response,
|
||||
'<div class="js-inline-admin-formset inline-group" id="question_set-group"'
|
||||
)
|
||||
# The right callable should be used for the inline readonly_fields
|
||||
# column cells
|
||||
self.assertContains(response, '<p>Callable in QuestionInline</p>')
|
||||
@ -548,7 +554,7 @@ class TestInlinePermissions(TestCase):
|
||||
response = self.client.get(reverse('admin:admin_inlines_author_add'))
|
||||
# No change permission on books, so no inline
|
||||
self.assertNotContains(response, '<h2>Author-book relationships</h2>')
|
||||
self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
|
||||
self.assertNotContains(response, 'Add another Author-Book Relationship')
|
||||
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
|
||||
|
||||
def test_inline_add_fk_noperm(self):
|
||||
@ -562,7 +568,7 @@ class TestInlinePermissions(TestCase):
|
||||
response = self.client.get(self.author_change_url)
|
||||
# No change permission on books, so no inline
|
||||
self.assertNotContains(response, '<h2>Author-book relationships</h2>')
|
||||
self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
|
||||
self.assertNotContains(response, 'Add another Author-Book Relationship')
|
||||
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
|
||||
|
||||
def test_inline_change_fk_noperm(self):
|
||||
@ -578,7 +584,7 @@ class TestInlinePermissions(TestCase):
|
||||
response = self.client.get(reverse('admin:admin_inlines_author_add'))
|
||||
# No change permission on Books, so no inline
|
||||
self.assertNotContains(response, '<h2>Author-book relationships</h2>')
|
||||
self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
|
||||
self.assertNotContains(response, 'Add another Author-Book Relationship')
|
||||
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
|
||||
|
||||
def test_inline_add_fk_add_perm(self):
|
||||
@ -597,7 +603,7 @@ class TestInlinePermissions(TestCase):
|
||||
response = self.client.get(self.author_change_url)
|
||||
# No change permission on books, so no inline
|
||||
self.assertNotContains(response, '<h2>Author-book relationships</h2>')
|
||||
self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
|
||||
self.assertNotContains(response, 'Add another Author-Book Relationship')
|
||||
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
|
||||
self.assertNotContains(response, 'id="id_Author_books-0-DELETE"')
|
||||
|
||||
@ -607,7 +613,7 @@ class TestInlinePermissions(TestCase):
|
||||
response = self.client.get(self.author_change_url)
|
||||
# We have change perm on books, so we can add/change/delete inlines
|
||||
self.assertContains(response, '<h2>Author-book relationships</h2>')
|
||||
self.assertContains(response, 'Add another Author\\u002Dbook relationship')
|
||||
self.assertContains(response, 'Add another Author-book relationship')
|
||||
self.assertContains(response, '<input type="hidden" id="id_Author_books-TOTAL_FORMS" '
|
||||
'value="4" name="Author_books-TOTAL_FORMS" />', html=True)
|
||||
self.assertContains(
|
||||
|
@ -2,6 +2,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import unittest
|
||||
@ -260,8 +261,7 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
||||
'date_1': '14:55:39',
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_article_add'), post_data)
|
||||
self.assertContains(response, 'dismissAddRelatedObjectPopup')
|
||||
self.assertContains(response, 'title with a new\\u000Aline')
|
||||
self.assertContains(response, 'title with a new\\nline')
|
||||
|
||||
def test_basic_edit_POST(self):
|
||||
"""
|
||||
@ -734,7 +734,7 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
||||
"""
|
||||
actor = Actor.objects.create(name="Palin", age=27)
|
||||
response = self.client.get("%s?%s" % (reverse('admin:admin_views_actor_changelist'), IS_POPUP_VAR))
|
||||
self.assertContains(response, "opener.dismissRelatedLookupPopup(window, '%s')" % actor.pk)
|
||||
self.assertContains(response, 'data-popup-opener="%s"' % actor.pk)
|
||||
|
||||
def test_hide_change_password(self):
|
||||
"""
|
||||
@ -3445,27 +3445,23 @@ action)</option>
|
||||
self.assertEqual(response.template_name, 'admin/popup_response.html')
|
||||
|
||||
def test_popup_template_escaping(self):
|
||||
context = {
|
||||
popup_response_data = json.dumps({
|
||||
'new_value': 'new_value\\',
|
||||
'obj': 'obj\\',
|
||||
'value': 'value\\',
|
||||
})
|
||||
context = {
|
||||
'popup_response_data': popup_response_data,
|
||||
}
|
||||
output = render_to_string('admin/popup_response.html', context)
|
||||
self.assertIn(
|
||||
'opener.dismissAddRelatedObjectPopup(window, "value\\u005C", "obj\\u005C");', output
|
||||
r'"value\\"', output
|
||||
)
|
||||
|
||||
context['action'] = 'change'
|
||||
output = render_to_string('admin/popup_response.html', context)
|
||||
self.assertIn(
|
||||
'opener.dismissChangeRelatedObjectPopup(window, '
|
||||
'"value\\u005C", "obj\\u005C", "new_value\\u005C");', output
|
||||
r'"new_value\\"', output
|
||||
)
|
||||
|
||||
context['action'] = 'delete'
|
||||
output = render_to_string('admin/popup_response.html', context)
|
||||
self.assertIn(
|
||||
'opener.dismissDeleteRelatedObjectPopup(window, "value\\u005C");', output
|
||||
r'"obj\\"', output
|
||||
)
|
||||
|
||||
|
||||
@ -4273,16 +4269,19 @@ class PrePopulatedTest(TestCase):
|
||||
|
||||
def test_prepopulated_on(self):
|
||||
response = self.client.get(reverse('admin:admin_views_prepopulatedpost_add'))
|
||||
self.assertContains(response, "id: '#id_slug',")
|
||||
self.assertContains(response, "field['dependency_ids'].push('#id_title');")
|
||||
self.assertContains(response, "id: '#id_prepopulatedsubpost_set-0-subslug',")
|
||||
self.assertContains(response, ""id": "#id_slug"")
|
||||
self.assertContains(response, ""dependency_ids": ["#id_title"]")
|
||||
self.assertContains(response, ""id": "#id_prepopulatedsubpost_set-0-subslug"")
|
||||
|
||||
def test_prepopulated_off(self):
|
||||
response = self.client.get(reverse('admin:admin_views_prepopulatedpost_change', args=(self.p1.pk,)))
|
||||
self.assertContains(response, "A Long Title")
|
||||
self.assertNotContains(response, "id: '#id_slug'")
|
||||
self.assertNotContains(response, "field['dependency_ids'].push('#id_title');")
|
||||
self.assertNotContains(response, "id: '#id_prepopulatedsubpost_set-0-subslug',")
|
||||
self.assertNotContains(response, ""id": "#id_slug"")
|
||||
self.assertNotContains(response, ""dependency_ids": ["#id_title"]")
|
||||
self.assertNotContains(
|
||||
response,
|
||||
""id": "#id_prepopulatedsubpost_set-0-subslug""
|
||||
)
|
||||
|
||||
@override_settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True)
|
||||
def test_prepopulated_maxlength_localized(self):
|
||||
@ -4291,7 +4290,7 @@ class PrePopulatedTest(TestCase):
|
||||
that maxLength (in the JavaScript) is rendered without separators.
|
||||
"""
|
||||
response = self.client.get(reverse('admin:admin_views_prepopulatedpostlargeslug_add'))
|
||||
self.assertContains(response, "maxLength: 1000") # instead of 1,000
|
||||
self.assertContains(response, ""maxLength": 1000") # instead of 1,000
|
||||
|
||||
|
||||
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'],
|
||||
@ -4933,7 +4932,7 @@ class UserAdminTest(TestCase):
|
||||
'_save': '1',
|
||||
}
|
||||
response = self.client.post(reverse('admin:auth_user_add') + '?_popup=1', data, follow=True)
|
||||
self.assertContains(response, 'dismissAddRelatedObjectPopup')
|
||||
self.assertContains(response, '"obj": "newuser"')
|
||||
|
||||
def test_user_fk_change_popup(self):
|
||||
"""User change through a FK popup should return the appropriate JavaScript response."""
|
||||
@ -4957,7 +4956,8 @@ class UserAdminTest(TestCase):
|
||||
'_save': '1',
|
||||
}
|
||||
response = self.client.post(url, data, follow=True)
|
||||
self.assertContains(response, 'dismissChangeRelatedObjectPopup')
|
||||
self.assertContains(response, '"obj": "newuser"')
|
||||
self.assertContains(response, '"action": "change"')
|
||||
|
||||
def test_user_fk_delete_popup(self):
|
||||
"""User deletion through a FK popup should return the appropriate JavaScript response."""
|
||||
@ -4973,7 +4973,7 @@ class UserAdminTest(TestCase):
|
||||
'_popup': '1',
|
||||
}
|
||||
response = self.client.post(url, data, follow=True)
|
||||
self.assertContains(response, 'dismissDeleteRelatedObjectPopup')
|
||||
self.assertContains(response, '"action": "delete"')
|
||||
|
||||
def test_save_add_another_button(self):
|
||||
user_count = User.objects.count()
|
||||
|
@ -271,9 +271,8 @@ class FilteredSelectMultipleWidgetTest(SimpleTestCase):
|
||||
w = widgets.FilteredSelectMultiple('test\\', False)
|
||||
self.assertHTMLEqual(
|
||||
w.render('test', 'test'),
|
||||
'<select multiple="multiple" name="test" class="selectfilter">\n</select>'
|
||||
'<script type="text/javascript">addEvent(window, "load", function(e) '
|
||||
'{SelectFilter.init("id_test", "test\\u005C", 0); });</script>\n'
|
||||
'<select multiple="multiple" name="test" class="selectfilter" '
|
||||
'data-field-name="test\\" data-is-stacked="0">\n</select>'
|
||||
)
|
||||
|
||||
def test_stacked_render(self):
|
||||
@ -281,9 +280,8 @@ class FilteredSelectMultipleWidgetTest(SimpleTestCase):
|
||||
w = widgets.FilteredSelectMultiple('test\\', True)
|
||||
self.assertHTMLEqual(
|
||||
w.render('test', 'test'),
|
||||
'<select multiple="multiple" name="test" class="selectfilterstacked">\n</select>'
|
||||
'<script type="text/javascript">addEvent(window, "load", function(e) '
|
||||
'{SelectFilter.init("id_test", "test\\u005C", 1); });</script>\n'
|
||||
'<select multiple="multiple" name="test" class="selectfilterstacked" '
|
||||
'data-field-name="test\\" data-is-stacked="1">\n</select>'
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user