Update polymorphic inline script.
Now based on standard Django script. This makes upgrading it to newer Django versions much easier. Changes for polymorphic are included in this commitfix_request_path_info
parent
249fc0088b
commit
60db4f63b9
|
|
@ -31,7 +31,7 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
#: This can be redefined for subclasses.
|
#: This can be redefined for subclasses.
|
||||||
polymorphic_media = Media(
|
polymorphic_media = Media(
|
||||||
js=(
|
js=(
|
||||||
'polymorphic/js/jquery.django-inlines.js',
|
'polymorphic/js/polymorphic_inlines.js',
|
||||||
),
|
),
|
||||||
css={
|
css={
|
||||||
'all': (
|
'all': (
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
.add-row-choice {
|
.polymorphic-add-choice {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-row-choice a:focus {
|
.polymorphic-add-choice a:focus {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-row .inline-type-choice {
|
.polymorphic-type-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2.2em;
|
top: 2.2em;
|
||||||
left: 0.5em;
|
left: 0.5em;
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-row .inline-type-choice ul {
|
.polymorphic-type-menu ul {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-row .inline-type-choice li {
|
.polymorphic-type-menu li {
|
||||||
list-style: none inside none;
|
list-style: none inside none;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,440 +0,0 @@
|
||||||
/**
|
|
||||||
* jQuery plugin for Django inlines
|
|
||||||
*
|
|
||||||
* (c) 2011-2016 Diederik van der Boor, Apache 2 Licensed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($){
|
|
||||||
|
|
||||||
function DjangoInline(group, options) {
|
|
||||||
options = $.extend({}, $.fn.djangoInline.defaults, options);
|
|
||||||
|
|
||||||
this.group = group;
|
|
||||||
this.$group = $(group);
|
|
||||||
this.options = options;
|
|
||||||
|
|
||||||
options.prefix = options.prefix || this.$group.attr('id').replace(/-group$/, '');
|
|
||||||
|
|
||||||
if( options.formTemplate ) {
|
|
||||||
this.$form_template = $(options.formTemplate);
|
|
||||||
} else {
|
|
||||||
this.$form_template = this.$group.find(this.options.emptyFormSelector); // the extra item to construct new instances.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the add button if requested (null/undefined means auto select)
|
|
||||||
if(options.showAddButton !== false) {
|
|
||||||
var dominfo = this._getManagementForm();
|
|
||||||
if (dominfo.max_forms == null || dominfo.max_forms.value === '' || (dominfo.max_forms.value - dominfo.total_forms.value) > 0) {
|
|
||||||
this.createAddButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DjangoInline.prototype = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the add button
|
|
||||||
*/
|
|
||||||
createAddButton: function() {
|
|
||||||
var $addButton;
|
|
||||||
var myself = this;
|
|
||||||
if (this.options.childTypes) {
|
|
||||||
// Polymorphic inlines!
|
|
||||||
// The add button opens a menu.
|
|
||||||
var menu = '<div class="inline-type-choice" style="display: none;"><ul>';
|
|
||||||
for (var i = 0; i < this.options.childTypes.length; i++) {
|
|
||||||
var obj = this.options.childTypes[i];
|
|
||||||
menu += '<li><a href="#" data-type="' + obj.type + '">' + obj.name + '</a></li>';
|
|
||||||
}
|
|
||||||
menu += '</ul></div>';
|
|
||||||
$addButton = $('<div class="' + this.options.addCssClass + ' add-row-choice"><a href="#">' + this.options.addText + "</a>" + menu + "</div>");
|
|
||||||
this.$group.append($addButton);
|
|
||||||
|
|
||||||
$addButton.children('a').click($.proxy(this._onMenuToggle, this));
|
|
||||||
$addButton.find('li a').click(function(event){ myself._onMenuItemClick(event); });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Normal inlines
|
|
||||||
$addButton = $('<div class="' + this.options.addCssClass + '"><a href="#">' + this.options.addText + "</a></div>");
|
|
||||||
this.$group.append($addButton);
|
|
||||||
|
|
||||||
$addButton.find('a').click(function(event) { event.preventDefault(); myself.addForm() });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onMenuToggle: function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
var $menu = $(event.target).next('.inline-type-choice');
|
|
||||||
|
|
||||||
if(! $menu.is(':visible')) {
|
|
||||||
function hideMenu() {
|
|
||||||
$menu.slideUp();
|
|
||||||
$(document).unbind('click', hideMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).click(hideMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
$menu.slideToggle();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onMenuItemClick: function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var type = $(event.target).attr('data-type');
|
|
||||||
var empty_form_selector = this.options.emptyFormSelector + "[data-inline-type=" + type + "]";
|
|
||||||
this.addForm(empty_form_selector);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main action, add a new row.
|
|
||||||
* Allow to select a different form template (for polymorphic inlines)
|
|
||||||
*/
|
|
||||||
addForm: function(emptyFormSelector) {
|
|
||||||
var $form_template;
|
|
||||||
|
|
||||||
if(emptyFormSelector) {
|
|
||||||
$form_template = this.$group.find(emptyFormSelector);
|
|
||||||
if($form_template.length === 0) {
|
|
||||||
throw new Error("Form template '" + emptyFormSelector + "' not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(! this.$form_template || this.$form_template.length === 0) {
|
|
||||||
throw new Error("No empty form available. Define the 'form_template' setting or add an '.empty-form' element in the '" + this.options.prefix + "' formset group!");
|
|
||||||
}
|
|
||||||
|
|
||||||
$form_template = this.$form_template;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Django admin/media/js/inlines.js API is not public, or easy to use.
|
|
||||||
// Recoded the inline model dynamics.
|
|
||||||
var management_form = this._getManagementForm();
|
|
||||||
if(! management_form.total_forms) {
|
|
||||||
throw new Error("Missing '#" + this._getGroupFieldIdPrefix() + "-TOTAL_FORMS' field. Make sure the management form included!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a inline is presented in a complex table,
|
|
||||||
// the newFormTarget can be very useful to direct the output.
|
|
||||||
var container;
|
|
||||||
if(this.options.newFormTarget == null) {
|
|
||||||
container = $form_template.parent();
|
|
||||||
}
|
|
||||||
else if($.isFunction(this.options.newFormTarget)) {
|
|
||||||
container = this.options.newFormTarget.apply(this.group);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
container = this.$group.find(this.options.newFormTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(container === null || container.length === 0) {
|
|
||||||
throw new Error("No container found via custom 'newFormTarget' function!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone the item.
|
|
||||||
var new_index = management_form.total_forms.value;
|
|
||||||
var item_id = this._getFormId(new_index);
|
|
||||||
var newhtml = _getOuterHtml($form_template).replace(/__prefix__/g, new_index);
|
|
||||||
var newitem = $(newhtml).removeClass("empty-form").attr("id", item_id);
|
|
||||||
|
|
||||||
// Add it
|
|
||||||
container.append(newitem);
|
|
||||||
var formset_item = $("#" + item_id);
|
|
||||||
if( formset_item.length === 0 ) {
|
|
||||||
throw new Error("New FormSet item not found: #" + item_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
formset_item.data('djangoInlineIndex', new_index);
|
|
||||||
if(this.options.onAdd) {
|
|
||||||
this.options.onAdd.call(this.group, formset_item, new_index, this.options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update administration
|
|
||||||
management_form.total_forms.value++;
|
|
||||||
return formset_item;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFormAt: function(index) {
|
|
||||||
return $('#' + this._getFormId(index));
|
|
||||||
},
|
|
||||||
|
|
||||||
_getFormId: function(index) {
|
|
||||||
// The form container is expected by the numbered as #prefix-NR
|
|
||||||
return this.options.itemIdTemplate.replace('{prefix}', this.options.prefix).replace('{index}', index);
|
|
||||||
},
|
|
||||||
|
|
||||||
_getGroupFieldIdPrefix: function() {
|
|
||||||
// typically: #id_modelname
|
|
||||||
return this.options.autoId.replace('{prefix}', this.options.prefix);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the management form data.
|
|
||||||
*/
|
|
||||||
_getManagementForm: function() {
|
|
||||||
var group_id_prefix = this._getGroupFieldIdPrefix();
|
|
||||||
return {
|
|
||||||
// management form item
|
|
||||||
total_forms: $("#" + group_id_prefix + "-TOTAL_FORMS")[0],
|
|
||||||
max_forms: $("#" + group_id_prefix + "-MAX_NUM_FORMS")[0],
|
|
||||||
group_id_prefix: group_id_prefix
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getItemData: function(child_node) {
|
|
||||||
var formset_item = $(child_node).closest(this.options.itemsSelector);
|
|
||||||
if( formset_item.length === 0 ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the ID, using the id_template pattern.
|
|
||||||
// note that ^...$ is important, as a '-' char can occur multiple times with generic inlines (inlinetype-id / app-model-ctfield-ctfkfield-id)
|
|
||||||
var id = formset_item.attr("id");
|
|
||||||
var cap = (new RegExp('^' + this.options.itemIdTemplate.replace('{prefix}', '(.+?)').replace('{index}', '(\\d+)') + '$')).exec(id);
|
|
||||||
|
|
||||||
return {
|
|
||||||
formset_item: formset_item,
|
|
||||||
prefix: cap[1],
|
|
||||||
index: parseInt(cap[2], 0) // or parseInt(formset_item.data('djangoInlineIndex'))
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the meta-data of a single form.
|
|
||||||
*/
|
|
||||||
_getItemForm: function(child_node) {
|
|
||||||
var dominfo = this._getItemData(child_node);
|
|
||||||
if( dominfo === null ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var field_id_prefix = this._getGroupFieldIdPrefix() + "-" + dominfo.index;
|
|
||||||
return $.extend({}, dominfo, {
|
|
||||||
// Export settings data
|
|
||||||
field_id_prefix: field_id_prefix,
|
|
||||||
field_name_prefix: dominfo.prefix + '-' + dominfo.index,
|
|
||||||
|
|
||||||
// Item fields
|
|
||||||
pk_field: $('#' + field_id_prefix + '-' + this.options.pkFieldName),
|
|
||||||
delete_checkbox: $("#" + field_id_prefix + "-DELETE")
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a row
|
|
||||||
*/
|
|
||||||
removeForm: function(child_node)
|
|
||||||
{
|
|
||||||
// Get dom info
|
|
||||||
var management_form = this._getManagementForm();
|
|
||||||
var itemform = this._getItemForm(child_node);
|
|
||||||
if( itemform === null ) {
|
|
||||||
throw new Error("No form found for the selector '" + child_node.selector + "'!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var total_count = parseInt(management_form.total_forms.value, 0);
|
|
||||||
var has_pk_field = itemform.pk_field.length != 0;
|
|
||||||
|
|
||||||
if(this.options.onBeforeRemove) {
|
|
||||||
this.options.onBeforeRemove.call(this.group, itemform.formset_item, this.options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case there is a delete checkbox, save it.
|
|
||||||
if( itemform.delete_checkbox.length )
|
|
||||||
{
|
|
||||||
if(has_pk_field)
|
|
||||||
itemform.pk_field.insertAfter(management_form.total_forms);
|
|
||||||
itemform.delete_checkbox.attr('checked', true).insertAfter(management_form.total_forms).hide();
|
|
||||||
}
|
|
||||||
else if( has_pk_field && itemform.pk_field[0].value )
|
|
||||||
{
|
|
||||||
// Construct a delete checkbox on the fly.
|
|
||||||
itemform.pk_field.insertAfter(management_form.total_forms);
|
|
||||||
$('<input type="hidden" id="' + itemform.field_id_prefix + '-DELETE" name="' + itemform.field_name_prefix + '-DELETE" value="on">').insertAfter(itemform.total_forms);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Newly added item, renumber in reverse order
|
|
||||||
for( var i = itemform.index + 1; i < total_count; i++ )
|
|
||||||
{
|
|
||||||
this._renumberItem(this.getFormAt(i), i - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
management_form.total_forms.value--;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// And remove item
|
|
||||||
itemform.formset_item.remove();
|
|
||||||
|
|
||||||
if(this.options.onRemove) {
|
|
||||||
this.options.onRemove.call(this.group, itemform.formset_item, this.options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemform.formset_item;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Based on django/contrib/admin/media/js/inlines.js
|
|
||||||
_renumberItem: function($formset_item, new_index)
|
|
||||||
{
|
|
||||||
var id_regex = new RegExp("(" + this._getFormId('(\\d+|__prefix__)') + ")");
|
|
||||||
var replacement = this._getFormId(new_index);
|
|
||||||
$formset_item.data('djangoInlineIndex', new_index);
|
|
||||||
|
|
||||||
// Loop through the nodes.
|
|
||||||
// Getting them all at once turns out to be more efficient, then looping per level.
|
|
||||||
var nodes = $formset_item.add( $formset_item.find("*") );
|
|
||||||
for( var i = 0; i < nodes.length; i++ )
|
|
||||||
{
|
|
||||||
var node = nodes[i];
|
|
||||||
var $node = $(node);
|
|
||||||
|
|
||||||
var for_attr = $node.attr('for');
|
|
||||||
if( for_attr && for_attr.match(id_regex) ) {
|
|
||||||
$node.attr("for", for_attr.replace(id_regex, replacement));
|
|
||||||
}
|
|
||||||
|
|
||||||
if( node.id && node.id.match(id_regex) ) {
|
|
||||||
node.id = node.id.replace(id_regex, replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( node.name && node.name.match(id_regex) ) {
|
|
||||||
node.name = node.name.replace(id_regex, replacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Extra query methods for external callers:
|
|
||||||
|
|
||||||
getFormIndex: function(child_node) {
|
|
||||||
var dominfo = this._getItemData(child_node);
|
|
||||||
return dominfo ? dominfo.index : null;
|
|
||||||
},
|
|
||||||
|
|
||||||
getForms: function() {
|
|
||||||
// typically: .inline-related:not(.empty-form)
|
|
||||||
return this.$group.children(this.options.itemsSelector + ":not(" + this.options.emptyFormSelector + ")");
|
|
||||||
},
|
|
||||||
|
|
||||||
getEmptyForm: function() {
|
|
||||||
// typically: #modelname-group > .empty-form
|
|
||||||
return this.$form_template;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFieldIdPrefix: function(item_index) {
|
|
||||||
if(! $.isNumeric(item_index)) {
|
|
||||||
var dominfo = this._getItemData(item_index);
|
|
||||||
if(dominfo === null) {
|
|
||||||
throw new Error("Unexpected element in getFieldIdPrefix, needs to be item_index, or DOM child node.");
|
|
||||||
}
|
|
||||||
item_index = dominfo.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// typically: #id_modelname-NN
|
|
||||||
return this._getGroupFieldIdPrefix() + "-" + item_index;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFieldsAt: function(index) {
|
|
||||||
var $form = this.getFormAt(index);
|
|
||||||
return this.getFields($form);
|
|
||||||
},
|
|
||||||
|
|
||||||
getFields: function(child_node) {
|
|
||||||
// Return all fields in a simple lookup object, with the prefix stripped.
|
|
||||||
var dominfo = this._getItemData(child_node);
|
|
||||||
if(dominfo === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fields = {};
|
|
||||||
var $inputs = dominfo.formset_item.find(':input');
|
|
||||||
var name_prefix = this.prefix + "-" + dominfo.index;
|
|
||||||
|
|
||||||
for(var i = 0; i < $inputs.length; i++) {
|
|
||||||
var name = $inputs[i].name;
|
|
||||||
if(name.substring(0, name_prefix.length) == name_prefix) {
|
|
||||||
var suffix = name.substring(name_prefix.length + 1); // prefix-<name>
|
|
||||||
fields[suffix] = $inputs[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields;
|
|
||||||
},
|
|
||||||
|
|
||||||
removeFormAt: function(index) {
|
|
||||||
return this.removeForm(this.getFormAt(index));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function _getOuterHtml($node)
|
|
||||||
{
|
|
||||||
if( $node.length )
|
|
||||||
{
|
|
||||||
if( $node[0].outerHTML ) {
|
|
||||||
return $node[0].outerHTML;
|
|
||||||
} else {
|
|
||||||
return $("<div>").append($node.clone()).html();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// jQuery plugin definition
|
|
||||||
// Separated from the main code, as demonstrated by Twitter bootstrap.
|
|
||||||
$.fn.djangoInline = function(option) {
|
|
||||||
var args = Array.prototype.splice.call(arguments, 1);
|
|
||||||
var call_method = (typeof option == 'string');
|
|
||||||
var plugin_result = (call_method ? undefined : this);
|
|
||||||
|
|
||||||
this.filter('.inline-group').each(function() {
|
|
||||||
var $this = $(this);
|
|
||||||
var data = $this.data('djangoInline');
|
|
||||||
|
|
||||||
if (! data) {
|
|
||||||
var options = typeof option == 'object' ? option : {};
|
|
||||||
$this.data('djangoInline', (data = new DjangoInline(this, options)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof option == 'string') {
|
|
||||||
plugin_result = data[option].apply(data, args);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return plugin_result;
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.djangoInline.defaults = {
|
|
||||||
pkFieldName: 'id', // can be `tablename_ptr` for inherited models.
|
|
||||||
autoId: 'id_{prefix}', // the auto id format used in Django.
|
|
||||||
prefix: null, // typically the model name in lower case.
|
|
||||||
newFormTarget: null, // Define where the row should be added; a CSS selector or function.
|
|
||||||
|
|
||||||
itemIdTemplate: '{prefix}-{index}', // Format of the ID attribute.
|
|
||||||
itemsSelector: '.inline-related', // CSS class that each item has
|
|
||||||
emptyFormSelector: '.empty-form', // CSS class that
|
|
||||||
|
|
||||||
formTemplate: null, // Complete HTML of the new form
|
|
||||||
childTypes: null, // Extra for django-polymorphic, allow a choice between empty-forms.
|
|
||||||
|
|
||||||
showAddButton: true,
|
|
||||||
addText: "add another", // Text for the add link
|
|
||||||
deleteText: "remove", // Text for the delete link
|
|
||||||
addCssClass: "add-row" // CSS class applied to the add link
|
|
||||||
};
|
|
||||||
|
|
||||||
// Also expose inner object
|
|
||||||
$.fn.djangoInline.Constructor = DjangoInline;
|
|
||||||
|
|
||||||
|
|
||||||
// Auto enable inlines
|
|
||||||
$.fn.ready(function(){
|
|
||||||
$('.js-jquery-django-inlines').each(function(){
|
|
||||||
var $this = $(this);
|
|
||||||
var data = $this.data();
|
|
||||||
var inlineOptions = data.inlineFormset;
|
|
||||||
$this.djangoInline(inlineOptions.options)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
})(window.django ? window.django.jQuery : jQuery);
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
/*global DateTimeShortcuts, SelectFilter*/
|
/*global DateTimeShortcuts, SelectFilter*/
|
||||||
|
|
||||||
|
// This is a slightly adapted version of Django's inlines.js
|
||||||
|
// Forked for polymorphic by Diederik van der Boor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Django admin inlines
|
* Django admin inlines
|
||||||
*
|
*
|
||||||
|
|
@ -17,8 +21,8 @@
|
||||||
*/
|
*/
|
||||||
(function($) {
|
(function($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
$.fn.formset = function(opts) {
|
$.fn.polymorphicFormset = function(opts) {
|
||||||
var options = $.extend({}, $.fn.formset.defaults, opts);
|
var options = $.extend({}, $.fn.polymorphicFormset.defaults, opts);
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $parent = $this.parent();
|
var $parent = $this.parent();
|
||||||
var updateElementIndex = function(el, prefix, ndx) {
|
var updateElementIndex = function(el, prefix, ndx) {
|
||||||
|
|
@ -44,23 +48,55 @@
|
||||||
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||||
});
|
});
|
||||||
if ($this.length && showAddButton) {
|
if ($this.length && showAddButton) {
|
||||||
var addButton = options.addButton;
|
var addContainer;
|
||||||
if (addButton === null) {
|
var menuButton;
|
||||||
|
var addButtons;
|
||||||
|
|
||||||
|
// For Polymorphic inlines, the add button opens a menu.
|
||||||
|
var menu = '<div class="polymorphic-type-menu" style="display: none;"><ul>';
|
||||||
|
for (var i = 0; i < options.childTypes.length; i++) {
|
||||||
|
var obj = options.childTypes[i];
|
||||||
|
menu += '<li><a href="#" data-type="' + obj.type + '">' + obj.name + '</a></li>';
|
||||||
|
}
|
||||||
|
menu += '</ul></div>';
|
||||||
|
|
||||||
if ($this.prop("tagName") === "TR") {
|
if ($this.prop("tagName") === "TR") {
|
||||||
// If forms are laid out as table rows, insert the
|
// If forms are laid out as table rows, insert the
|
||||||
// "add" button in a new table row:
|
// "add" button in a new table row:
|
||||||
var numCols = this.eq(-1).children().length;
|
var numCols = this.eq(-1).children().length;
|
||||||
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="#">' + options.addText + "</a></tr>");
|
$parent.append('<tr class="' + options.addCssClass + ' polymorphic-add-choice"><td colspan="' + numCols + '"><a href="#">' + options.addText + "</a>" + menu + "</tr>");
|
||||||
addButton = $parent.find("tr:last a");
|
addContainer = $parent.find("tr:last > td");
|
||||||
|
menuButton = addContainer.children('a');
|
||||||
|
addButtons = addContainer.find("li a");
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, insert it immediately after the last form:
|
// Otherwise, insert it immediately after the last form:
|
||||||
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="#">' + options.addText + "</a></div>");
|
$this.filter(":last").after('<div class="' + options.addCssClass + ' polymorphic-add-choice"><a href="#">' + options.addText + "</a>" + menu + "</div>");
|
||||||
addButton = $this.filter(":last").next().find("a");
|
addContainer = $this.filter(":last").next();
|
||||||
|
menuButton = addContainer.children('a');
|
||||||
|
addButtons = addContainer.find("li a");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menuButton.click(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation(); // for menu hide
|
||||||
|
var $menu = $(event.target).next('.polymorphic-type-menu');
|
||||||
|
|
||||||
|
if(! $menu.is(':visible')) {
|
||||||
|
var hideMenu = function() {
|
||||||
|
$menu.slideUp(50);
|
||||||
|
$(document).unbind('click', hideMenu);
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).click(hideMenu);
|
||||||
}
|
}
|
||||||
addButton.click(function(e) {
|
|
||||||
e.preventDefault();
|
$menu.slideToggle(50);
|
||||||
var template = $("#" + options.prefix + "-empty");
|
});
|
||||||
|
|
||||||
|
addButtons.click(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var polymorphicType = $(event.target).attr('data-type'); // Select polymorphic type.
|
||||||
|
var template = $("#" + polymorphicType + "-empty");
|
||||||
var row = template.clone(true);
|
var row = template.clone(true);
|
||||||
row.removeClass(options.emptyCssClass)
|
row.removeClass(options.emptyCssClass)
|
||||||
.addClass(options.formCssClass)
|
.addClass(options.formCssClass)
|
||||||
|
|
@ -88,7 +124,7 @@
|
||||||
nextIndex += 1;
|
nextIndex += 1;
|
||||||
// Hide add button in case we've hit the max, except we want to add infinitely
|
// Hide add button in case we've hit the max, except we want to add infinitely
|
||||||
if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
|
if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
|
||||||
addButton.parent().hide();
|
addButtons.parent().hide();
|
||||||
}
|
}
|
||||||
// The delete button of each row triggers a bunch of other things
|
// The delete button of each row triggers a bunch of other things
|
||||||
row.find("a." + options.deleteCssClass).click(function(e1) {
|
row.find("a." + options.deleteCssClass).click(function(e1) {
|
||||||
|
|
@ -106,7 +142,7 @@
|
||||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||||
// Show add button again once we drop below max
|
// Show add button again once we drop below max
|
||||||
if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) {
|
if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) {
|
||||||
addButton.parent().show();
|
addButtons.parent().show();
|
||||||
}
|
}
|
||||||
// Also, update names and ids for all remaining form controls
|
// Also, update names and ids for all remaining form controls
|
||||||
// so they remain in sequence:
|
// so they remain in sequence:
|
||||||
|
|
@ -130,9 +166,10 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Setup plugin defaults */
|
/* Setup plugin defaults */
|
||||||
$.fn.formset.defaults = {
|
$.fn.polymorphicFormset.defaults = {
|
||||||
prefix: "form", // The form prefix for your django formset
|
prefix: "form", // The form prefix for your django formset
|
||||||
addText: "add another", // Text for the add link
|
addText: "add another", // Text for the add link
|
||||||
|
childTypes: null, // defined by the client.
|
||||||
deleteText: "remove", // Text for the delete link
|
deleteText: "remove", // Text for the delete link
|
||||||
addCssClass: "add-row", // CSS class applied to the add link
|
addCssClass: "add-row", // CSS class applied to the add link
|
||||||
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
||||||
|
|
@ -145,7 +182,7 @@
|
||||||
|
|
||||||
|
|
||||||
// Tabular inlines ---------------------------------------------------------
|
// Tabular inlines ---------------------------------------------------------
|
||||||
$.fn.tabularFormset = function(options) {
|
$.fn.tabularPolymorphicFormset = function(options) {
|
||||||
var $rows = $(this);
|
var $rows = $(this);
|
||||||
var alternatingRows = function(row) {
|
var alternatingRows = function(row) {
|
||||||
$($rows.selector).not(".add-row").removeClass("row1 row2")
|
$($rows.selector).not(".add-row").removeClass("row1 row2")
|
||||||
|
|
@ -191,9 +228,10 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$rows.formset({
|
$rows.polymorphicFormset({
|
||||||
prefix: options.prefix,
|
prefix: options.prefix,
|
||||||
addText: options.addText,
|
addText: options.addText,
|
||||||
|
childTypes: options.childTypes,
|
||||||
formCssClass: "dynamic-" + options.prefix,
|
formCssClass: "dynamic-" + options.prefix,
|
||||||
deleteCssClass: "inline-deletelink",
|
deleteCssClass: "inline-deletelink",
|
||||||
deleteText: options.deleteText,
|
deleteText: options.deleteText,
|
||||||
|
|
@ -212,7 +250,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stacked inlines ---------------------------------------------------------
|
// Stacked inlines ---------------------------------------------------------
|
||||||
$.fn.stackedFormset = function(options) {
|
$.fn.stackedPolymorphicFormset = function(options) {
|
||||||
var $rows = $(this);
|
var $rows = $(this);
|
||||||
var updateInlineLabel = function(row) {
|
var updateInlineLabel = function(row) {
|
||||||
$($rows.selector).find(".inline_label").each(function(i) {
|
$($rows.selector).find(".inline_label").each(function(i) {
|
||||||
|
|
@ -258,9 +296,10 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$rows.formset({
|
$rows.polymorphicFormset({
|
||||||
prefix: options.prefix,
|
prefix: options.prefix,
|
||||||
addText: options.addText,
|
addText: options.addText,
|
||||||
|
childTypes: options.childTypes,
|
||||||
formCssClass: "dynamic-" + options.prefix,
|
formCssClass: "dynamic-" + options.prefix,
|
||||||
deleteCssClass: "inline-deletelink",
|
deleteCssClass: "inline-deletelink",
|
||||||
deleteText: options.deleteText,
|
deleteText: options.deleteText,
|
||||||
|
|
@ -279,15 +318,15 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$(".js-inline-admin-formset").each(function() {
|
$(".js-inline-polymorphic-admin-formset").each(function() {
|
||||||
var data = $(this).data(),
|
var data = $(this).data(),
|
||||||
inlineOptions = data.inlineFormset;
|
inlineOptions = data.inlineFormset;
|
||||||
switch(data.inlineType) {
|
switch(data.inlineType) {
|
||||||
case "stacked":
|
case "stacked":
|
||||||
$(inlineOptions.name + "-group .inline-related").stackedFormset(inlineOptions.options);
|
$(inlineOptions.name + "-group .inline-related").stackedPolymorphicFormset(inlineOptions.options);
|
||||||
break;
|
break;
|
||||||
case "tabular":
|
case "tabular":
|
||||||
$(inlineOptions.name + "-group .tabular.inline-related tbody tr").tabularFormset(inlineOptions.options);
|
$(inlineOptions.name + "-group .tabular.inline-related tbody tr").tabularPolymorphicFormset(inlineOptions.options);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{% load i18n admin_urls static %}
|
{% load i18n admin_urls static %}
|
||||||
|
|
||||||
<div class="js-jquery-django-inlines inline-group"
|
<div class="js-inline-polymorphic-admin-formset inline-group"
|
||||||
id="{{ inline_admin_formset.formset.prefix }}-group"
|
id="{{ inline_admin_formset.formset.prefix }}-group"
|
||||||
data-inline-type="stacked"
|
data-inline-type="stacked"
|
||||||
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
|
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
|
||||||
|
|
@ -12,8 +12,7 @@
|
||||||
|
|
||||||
{% for inline_admin_form in inline_admin_formset %}
|
{% for inline_admin_form in inline_admin_formset %}
|
||||||
<div class="inline-related inline-{{ inline_admin_form.model_admin.opts.model_name }}{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if not inline_admin_form.original.pk %} empty-form {% endif %}{% if forloop.last %} last-related{% endif %}"
|
<div class="inline-related inline-{{ inline_admin_form.model_admin.opts.model_name }}{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if not inline_admin_form.original.pk %} empty-form {% endif %}{% if forloop.last %} last-related{% endif %}"
|
||||||
id="{{ inline_admin_formset.formset.prefix }}-{% if inline_admin_form.original.pk %}{{ forloop.counter0 }}{% else %}empty{% endif %}"
|
id="{% if inline_admin_form.original.pk %}{{ inline_admin_formset.formset.prefix }}-{{ forloop.counter0 }}{% else %}{{ inline_admin_form.model_admin.opts.model_name }}-empty{% endif %}">
|
||||||
data-inline-type="{{ inline_admin_form.model_admin.opts.model_name }}">
|
|
||||||
|
|
||||||
<h3><b>{{ inline_admin_form.model_admin.opts.verbose_name|capfirst }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="inlinechangelink">{% trans "Change" %}</a>{% endif %}
|
<h3><b>{{ inline_admin_form.model_admin.opts.verbose_name|capfirst }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="inlinechangelink">{% trans "Change" %}</a>{% endif %}
|
||||||
{% else %}#{{ forloop.counter }}{% endif %}</span>
|
{% else %}#{{ forloop.counter }}{% endif %}</span>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue