Add preliminairy working JavaScript to render polymorphic inlines
parent
1f0ddd8436
commit
7330a4f099
|
|
@ -2,4 +2,5 @@ include README.rst
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include DOCS.rst
|
include DOCS.rst
|
||||||
include CHANGES.rst
|
include CHANGES.rst
|
||||||
|
recursive-include polymorphic/static *.js *.css
|
||||||
recursive-include polymorphic/templates *
|
recursive-include polymorphic/templates *
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@ Rendering utils for admin forms;
|
||||||
|
|
||||||
This makes sure that admin fieldsets/layout settings are exported to the template.
|
This makes sure that admin fieldsets/layout settings are exported to the template.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm, AdminField
|
from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm, AdminField
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.text import capfirst
|
||||||
|
from django.utils.translation import ugettext
|
||||||
|
|
||||||
from polymorphic.formsets import BasePolymorphicModelFormSet
|
from polymorphic.formsets import BasePolymorphicModelFormSet
|
||||||
|
|
||||||
|
|
@ -14,6 +19,9 @@ class PolymorphicInlineAdminForm(InlineAdminForm):
|
||||||
Expose the admin configuration for a form
|
Expose the admin configuration for a form
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def polymorphic_ctype_field(self):
|
||||||
|
return AdminField(self.form, 'polymorphic_ctype', False)
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
"""
|
"""
|
||||||
|
|
@ -71,6 +79,30 @@ class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
|
||||||
fields.update(child_inline.get_prepopulated_fields(self.request, self.obj))
|
fields.update(child_inline.get_prepopulated_fields(self.request, self.obj))
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
# The polymorphic template follows the same method like all other inlines do in Django 1.10.
|
||||||
|
# This method is added for compatibility with older Django versions.
|
||||||
|
def inline_formset_data(self):
|
||||||
|
"""
|
||||||
|
A JavaScript data structure for the JavaScript code
|
||||||
|
"""
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
'childTypes': [
|
||||||
|
{
|
||||||
|
'type': model._meta.model_name,
|
||||||
|
'name': force_text(model._meta.verbose_name)
|
||||||
|
} for model in self.formset.child_forms.keys()
|
||||||
|
],
|
||||||
|
'deleteText': ugettext('Remove'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class PolymorphicInlineSupportMixin(object):
|
class PolymorphicInlineSupportMixin(object):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,22 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):
|
||||||
* Permissions are only checked on the base model.
|
* Permissions are only checked on the base model.
|
||||||
* The child inlines can't override the base model fields, only this parent inline can do that.
|
* The child inlines can't override the base model fields, only this parent inline can do that.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formset = BasePolymorphicInlineFormSet
|
formset = BasePolymorphicInlineFormSet
|
||||||
|
|
||||||
|
#: The extra media to add for the polymorphic inlines effect.
|
||||||
|
#: This can be redefined for subclasses.
|
||||||
|
polymorphic_media = Media(
|
||||||
|
js=(
|
||||||
|
'polymorphic/js/jquery.django-inlines.js',
|
||||||
|
),
|
||||||
|
css={
|
||||||
|
'all': (
|
||||||
|
'polymorphic/css/polymorphic_inlines.css',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
#: The extra forms to show
|
#: The extra forms to show
|
||||||
#: By default there are no 'extra' forms as the desired type is unknown.
|
#: By default there are no 'extra' forms as the desired type is unknown.
|
||||||
#: Instead, add each new item using JavaScript that first offers a type-selection.
|
#: Instead, add each new item using JavaScript that first offers a type-selection.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class PolymorphicFormSetChild(object):
|
||||||
# This is mostly needed for the generic inline formsets
|
# This is mostly needed for the generic inline formsets
|
||||||
self._form_base = form
|
self._form_base = form
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
self.exclude = exclude
|
self.exclude = exclude or ()
|
||||||
self.formfield_callback = formfield_callback
|
self.formfield_callback = formfield_callback
|
||||||
self.widgets = widgets
|
self.widgets = widgets
|
||||||
self.localized_fields = localized_fields
|
self.localized_fields = localized_fields
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
.add-row-choice {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-row-choice a:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-row .inline-type-choice {
|
||||||
|
position: absolute;
|
||||||
|
top: 2.2em;
|
||||||
|
left: 0.5em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-row .inline-type-choice ul {
|
||||||
|
padding: 2px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-row .inline-type-choice li {
|
||||||
|
list-style: none inside none;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,440 @@
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% load i18n admin_urls static %}
|
||||||
|
|
||||||
|
<div class="js-jquery-django-inlines inline-group"
|
||||||
|
id="{{ inline_admin_formset.formset.prefix }}-group"
|
||||||
|
data-inline-type="stacked"
|
||||||
|
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
|
||||||
|
|
||||||
|
<fieldset class="module {{ inline_admin_formset.classes }}">
|
||||||
|
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
|
||||||
|
{{ inline_admin_formset.formset.management_form }}
|
||||||
|
{{ inline_admin_formset.formset.non_form_errors }}
|
||||||
|
|
||||||
|
{% 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 %}"
|
||||||
|
id="{{ inline_admin_formset.formset.prefix }}-{% if inline_admin_form.original.pk %}{{ forloop.counter0 }}{% else %}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 %}
|
||||||
|
{% else %}#{{ forloop.counter }}{% endif %}</span>
|
||||||
|
{% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a>{% endif %}
|
||||||
|
{% if inline_admin_form.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
|
||||||
|
|
||||||
|
{% for fieldset in inline_admin_form %}
|
||||||
|
{% include "admin/includes/fieldset.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
||||||
|
|
||||||
|
{{ inline_admin_form.fk_field.field }}
|
||||||
|
{{ inline_admin_form.polymorphic_ctype_field.field }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
Loading…
Reference in New Issue