Created
October 4, 2011 07:25
-
-
Save hoheinzollern/1261073 to your computer and use it in GitHub Desktop.
Reordering of tabular inlines in Django administration website.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.contrib import admin | |
class SlaveInline(admin.TabularInline): | |
model = Slave | |
extra = 0 | |
class MasterAdmin(admin.ModelAdmin): | |
inlines = (SlaveInline,) | |
class Media: | |
js = ('https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js', | |
'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js', | |
'/media/script/dynamic_inlines_with_sort.js',) | |
css = { 'all' : ['css/dynamic_inlines_with_sort.css'], } | |
admin.site.register(Master, MasterAdmin) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* To make row height of saved items same as others */ | |
.inline-group .tabular tr.has_original td { padding-top:0.5em; } | |
.inline-group .tabular tr.has_original td.original p { display:none; } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* dynamic_inlines_with_sort.js */ | |
/* Created in May 2009 by Hannes Rydén */ | |
/* Modified in September 2011 by Alessandro Bruni */ | |
/* Use, distribute and modify freely */ | |
// "Add"-link html code. Defaults to Django's "+" image icon, but could use text instead. | |
add_link_html = '<img src="/media/img/admin/icon_addlink.gif" ' + | |
'width="10" height="10" alt="Add new row" style="margin:0.5em 1em;" />'; | |
// "Delete"-link html code. Defaults to Django's "x" image icon, but could use text instead. | |
delete_link_html = '<img src="/media/img/admin/icon_deletelink.gif" ' + | |
'width="10" height="10" alt="Delete row" style="margin-top:0.5em" />'; | |
position_field = 'order'; // Name of inline model field (integer) used for ordering. Defaults to "position". | |
jQuery(function($) { | |
// This script is applied to all TABULAR inlines | |
$('div.inline-group div.tabular').each(function() { | |
table = $(this).find('table'); | |
// Hide initial extra row and prepare it to be used as a template for new rows | |
add_template = table.find('tr.empty-form'); | |
add_template.addClass('add_template').hide(); | |
table.prepend(add_template); | |
// Remove original add button | |
table.find('tr.add-row').remove(); | |
// Hide initial deleted rows | |
table.find('td.delete input:checkbox:checked').parent('td').parent('tr').addClass('deleted_row').hide(); | |
// "Add"-button in bottom of inline for adding new rows | |
$(this).find('fieldset').after('<a class="add" href="#">' + add_link_html + '</a>'); | |
$(this).find('a.add').click(function(){ | |
new_item = add_template.clone(true); | |
create_delete_button(new_item.find('td.delete')); | |
new_item.removeClass('add_template').removeClass('empty-form').show(); | |
$(this).parent().find('table').append(new_item); | |
update_positions($(this).parent().find('table'), true); | |
// Place for special code to re-enable javascript widgets after clone (e.g. an ajax-autocomplete field) | |
// Fictive example: new_item.find('.autocomplete').each(function() { $(this).triggerHandler('autocomplete'); }); | |
}).removeAttr('href').css('cursor', 'pointer'); | |
// "Delete"-buttons for each row that replaces the default checkbox | |
table.find('tr:not(.add_template) td.delete').each(function() { | |
create_delete_button($(this)); | |
}); | |
// Drag and drop functionality - only used if a position field exists | |
if (position_field != '' && table.find('td').is('.' + position_field)) | |
{ | |
// Hide "position"-field (both td:s and th:s) | |
$(this).find('td.' + position_field).hide(); | |
td_pos_field_index = table.find('tbody tr td').index($(this).find('td.' + position_field)); | |
$(this).find('th:eq(' + (td_pos_field_index-1) + ')').hide(); | |
// Hide "original"-field and set any colspan to 1 (why show in the first case?) | |
$(this).find('td.original').hide(); | |
$(this).find('th[colspan]').removeAttr('colspan'); | |
// Make table sortable using jQuery UI Sortable | |
table.sortable({ | |
items: 'tr:has(td)', | |
tolerance: 'pointer', | |
axis: 'y', | |
cancel: 'input,button,select,a', | |
helper: 'clone', | |
update: function() { | |
update_positions($(this)); | |
} | |
}); | |
// Re-order <tr>:s based on the "position"-field values. | |
// This is a very simple ordering which only works with correct position number sequences, | |
// which the rest of this script (hopefully) guarantees. | |
rows = []; | |
table.find('tbody tr').each(function() { | |
position = $(this).find('td.' + position_field + ' input').val(); | |
rows[position] = $(this); | |
// Add move cursor to table row. | |
// Also remove row coloring, as it confuses when using drag-and-drop for ordering | |
table.find('tr:has(td)').css('cursor', 'move').removeClass('row1').removeClass('row2'); | |
}); | |
for (var i in rows) { table.append(rows[i]); } // Move <tr> to its correct position | |
update_positions($(this), true); | |
} | |
else | |
position_field = ''; | |
// Detach the template row | |
add_template.detach(); | |
}); | |
}); | |
// Function for creating fancy delete buttons | |
function create_delete_button(td) | |
{ | |
// Replace checkbox with image | |
td.find('input:checkbox').hide(); | |
td.append('<a class="delete" href="#">' + delete_link_html + '</a>'); | |
td.find('a.delete').click(function(){ | |
current_row = $(this).parent('td').parent('tr'); | |
table = current_row.parent().parent(); | |
if (current_row.is('.has_original')) // This row has already been saved once, so we must keep checkbox | |
{ | |
$(this).prev('input').attr('checked', true); | |
current_row.addClass('deleted_row').hide(); | |
} | |
else // This row has never been saved so we can just remove the element completely | |
{ | |
current_row.remove(); | |
} | |
update_positions(table, true); | |
}).removeAttr('href').css('cursor', 'pointer'); | |
} | |
// Updates "position"-field values based on row order in table | |
function update_positions(table, update_ids) | |
{ | |
even = true; | |
num_rows = 0 | |
position = 0; | |
// Set correct position: Filter through all trs, excluding first th tr and last hidden template tr | |
table.find('tbody tr:not(.add_template):not(.deleted_row)').each(function() { | |
if (position_field != '') | |
{ | |
// Update position field | |
$(this).find('td.' + position_field + ' input').val(position + 1); | |
position++; | |
} | |
else | |
{ | |
// Update row coloring | |
$(this).removeClass('row1 row2'); | |
if (even) | |
{ | |
$(this).addClass('row1'); | |
even = false; | |
} | |
else | |
{ | |
$(this).addClass('row2'); | |
even = true; | |
} | |
} | |
}); | |
table.find('tbody tr.has_original').each(function() { | |
num_rows++; | |
}); | |
table.find('tbody tr:not(.has_original):not(.add_template)').each(function() { | |
if (update_ids) update_id_fields($(this), num_rows); | |
num_rows++; | |
}); | |
table.find('tbody tr.add_template').each(function() { | |
if (update_ids) update_id_fields($(this), num_rows); | |
num_rows++; | |
}); | |
table.parent().parent('div.tabular').find("input[id$='TOTAL_FORMS']").val(num_rows); | |
} | |
// Updates actual id and name attributes of inputs, selects and so on. | |
// Required for Django validation to keep row order. | |
function update_id_fields(row, new_position) | |
{ | |
// Fix IDs, names etc. | |
// <select ...> | |
row.find('select').each(function() { | |
// id=... | |
old_id = $(this).attr('id').toString(); | |
new_id = old_id.replace(/([^ ]+\-)(?:\d+|__\w+__)(\-[^ ]+)/i, "$1" + new_position + "$2"); | |
$(this).attr('id', new_id); | |
// name=... | |
old_id = $(this).attr('name').toString(); | |
new_id = old_id.replace(/([^ ]+\-)(?:\d+|__\w+__)(\-[^ ]+)/i, "$1" + new_position + "$2"); | |
$(this).attr('name', new_id); | |
}); | |
// <input ...> | |
row.find('input').each(function() { | |
// id=... | |
old_id = $(this).attr('id').toString(); | |
new_id = old_id.replace(/([^ ]+\-)(?:\d+|__\w+__)(\-[^ ]+)/i, "$1" + new_position + "$2"); | |
$(this).attr('id', new_id); | |
// name=... | |
old_id = $(this).attr('name').toString(); | |
new_id = old_id.replace(/([^ ]+\-)(?:\d+|__\w+__)(\-[^ ]+)/i, "$1" + new_position + "$2"); | |
$(this).attr('name', new_id); | |
}); | |
// Are there other element types...? Add here. | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.db import models | |
class Master(models.Model): | |
pass | |
class Slave(models.Model): | |
master = models.ForeignKey(Master) | |
order = models.PositiveIntegerField() |
I don't even remember what I did with this code 11 years ago, please don't use it, or use it at your own risk.
const addInlineAddButton = function () {
if (addButton === null) {
if ($this.prop("tagName") === "TR") {
// If forms are laid out as table rows, insert the
// "add" button in a new table row:
// not using this numcol becuase im replacing tabular add-row tr to a div
// const numCols = $this.eq(-1).children().length;
($parent).parent().parent().find('h2').after('<div class="' + options.addCssClass + '"><a href="#">' + options.addText + "</a></div>"); <--------------from this line
addButton = ($parent).parent().parent().find("div.add-row a");
} else {
// Otherwise, insert it immediately after the last form:
// console.log($this.parent().find("h2"));
$this.parent().find("h2").after('<div class="' + options.addCssClass + '"><a href="#">' + options.addText + "</a></div>");
addButton = $this.parent().find("h2").next();
}
}
addButton.on('click', addInlineClickHandler);
};
const addInlineClickHandler = function (e) {
e.preventDefault();
const template = $("#" + options.prefix + "-empty");
const row = template.clone(true);
row.removeClass(options.emptyCssClass)
.addClass(options.formCssClass)
.attr("id", options.prefix + "-" + nextIndex);
addInlineDeleteButton(row);
row.find("*").each(function () {
updateElementIndex(this, options.prefix, totalForms.val());
});
// if tabular else stacked
if (String($(template).parent()[0].tagName) === 'TBODY') { <-----------this if block
row.prependTo($(template).parent());
} else {
$(template).parent().find('> input:last').after(row);
}
// Update number of total forms.
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
nextIndex += 1;
// Hide the add button if there's a limit and it's been reached.
if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
addButton.parent().hide();
}
// Show the remove buttons if there are more than min_num.
toggleDeleteButtonVisibility(row.closest('.inline-group'));
// Pass the new form to the post-add callback, if provided.
if (options.added) {
options.added(row);
}
$(document).trigger('formset:added', [row, options.prefix]);
};
Hy @hoheinzollern,
I customized inlines.js
for moving the button at the top of inline fieldsets
and now a new inline form will come at starting not ending.
is it a good solution for https://stackoverflow.com/questions/66409702/how-to-move-add-another-link-button-inline-to-the-top-django-admin
is there any error in this code?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
unfortunately, this is not working
i tried to open a ticket for it and they totally refuse it. https://code.djangoproject.com/ticket/33776