Skip to content

Commit 4d139d2

Browse files
committed
add buttons to move inline related forms to begin/end of fieldset
1 parent 8acc916 commit 4d139d2

File tree

5 files changed

+145
-3
lines changed

5 files changed

+145
-3
lines changed

adminsortable2/static/adminsortable2/css/sortable.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,23 @@ fieldset.sortable .inline-related.has_original h3 {
3030
background: url(../icons/drag.png) repeat;
3131
cursor: ns-resize;
3232
}
33+
fieldset.sortable :is(.inline-related, td.original) span.sort {
34+
float: right;
35+
margin-right: 10px;
36+
}
37+
fieldset.sortable :is(.inline-related, td.original) span.sort i {
38+
width: 0;
39+
height: 0;
40+
display: inline-block;
41+
border-style: solid;
42+
margin: 1px;
43+
cursor: pointer;
44+
}
45+
fieldset.sortable :is(.inline-related, td.original) span.sort i.move-begin {
46+
border-width: 0 5px 5px 5px;
47+
border-color: transparent transparent currentColor transparent;
48+
}
49+
fieldset.sortable :is(.inline-related, td.original) span.sort i.move-end {
50+
border-width: 5px 5px 0 5px;
51+
border-color: currentColor transparent transparent transparent;
52+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{% load i18n admin_urls %}
2+
<div class="js-inline-admin-formset inline-group"
3+
id="{{ inline_admin_formset.formset.prefix }}-group"
4+
data-inline-type="stacked"
5+
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
6+
<fieldset class="module {{ inline_admin_formset.classes }}">
7+
{% if inline_admin_formset.formset.max_num == 1 %}
8+
<h2>{{ inline_admin_formset.opts.verbose_name|capfirst }}</h2>
9+
{% else %}
10+
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
11+
{% endif %}
12+
{{ inline_admin_formset.formset.management_form }}
13+
{{ inline_admin_formset.formset.non_form_errors }}
14+
15+
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
16+
<h3><b>{{ inline_admin_formset.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="{% if inline_admin_formset.has_change_permission %}inlinechangelink{% else %}inlineviewlink{% endif %}">{% if inline_admin_formset.has_change_permission %}{% translate "Change" %}{% else %}{% translate "View" %}{% endif %}</a>{% endif %}
17+
{% else %}#{{ forloop.counter }}{% endif %}</span>
18+
{% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% translate "View on site" %}</a>{% endif %}
19+
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
20+
{% if inline_admin_formset.has_change_permission and inline_admin_form.original %}<span class="sort"><i class="move-begin"></i><i class="move-end"></i></span>{% endif %}
21+
</h3>
22+
{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
23+
{% for fieldset in inline_admin_form %}
24+
{% include "admin/includes/fieldset.html" %}
25+
{% endfor %}
26+
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
27+
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
28+
</div>{% endfor %}
29+
</fieldset>
30+
</div>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{% load i18n admin_urls static admin_modify %}
2+
<div class="js-inline-admin-formset inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"
3+
data-inline-type="tabular"
4+
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
5+
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
6+
{{ inline_admin_formset.formset.management_form }}
7+
<fieldset class="module {{ inline_admin_formset.classes }}">
8+
{% if inline_admin_formset.formset.max_num == 1 %}
9+
<h2>{{ inline_admin_formset.opts.verbose_name|capfirst }}</h2>
10+
{% else %}
11+
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
12+
{% endif %}
13+
{{ inline_admin_formset.formset.non_form_errors }}
14+
<table>
15+
<thead><tr>
16+
<th class="original"></th>
17+
{% for field in inline_admin_formset.fields %}
18+
<th class="column-{{ field.name }}{% if field.required %} required{% endif %}{% if field.widget.is_hidden %} hidden{% endif %}">{{ field.label|capfirst }}
19+
{% if field.help_text %}<img src="{% static "admin/img/icon-unknown.svg" %}" class="help help-tooltip" width="10" height="10" alt="({{ field.help_text|striptags }})" title="{{ field.help_text|striptags }}">{% endif %}
20+
</th>
21+
{% endfor %}
22+
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}<th>{% translate "Delete?" %}</th>{% endif %}
23+
</tr></thead>
24+
25+
<tbody>
26+
{% for inline_admin_form in inline_admin_formset %}
27+
{% if inline_admin_form.form.non_field_errors %}
28+
<tr class="row-form-errors"><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
29+
{% endif %}
30+
<tr class="form-row {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form{% endif %}"
31+
id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
32+
<td class="original">
33+
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
34+
{% if inline_admin_form.original %}
35+
{{ inline_admin_form.original }}
36+
{% 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="{% if inline_admin_formset.has_change_permission %}inlinechangelink{% else %}inlineviewlink{% endif %}">{% if inline_admin_formset.has_change_permission %}{% translate "Change" %}{% else %}{% translate "View" %}{% endif %}</a>{% endif %}
37+
{% endif %}
38+
{% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% translate "View on site" %}</a>{% endif %}
39+
<span class="sort"><i class="move-begin"></i><i class="move-end"></i></span>
40+
</p>{% endif %}
41+
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
42+
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
43+
</td>
44+
{% for fieldset in inline_admin_form %}
45+
{% for line in fieldset %}
46+
{% for field in line %}
47+
<td class="{% if field.field.name %}field-{{ field.field.name }}{% endif %}{% if field.field.is_hidden %} hidden{% endif %}">
48+
{% if field.is_readonly %}
49+
<p>{{ field.contents }}</p>
50+
{% else %}
51+
{{ field.field.errors.as_ul }}
52+
{{ field.field }}
53+
{% endif %}
54+
</td>
55+
{% endfor %}
56+
{% endfor %}
57+
{% endfor %}
58+
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}
59+
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
60+
{% endif %}
61+
</tr>
62+
{% endfor %}
63+
</tbody>
64+
</table>
65+
</fieldset>
66+
</div>
67+
</div>

client/admin-sortable2.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class InlineSortable {
219219
animation: 150,
220220
handle: 'td.original p',
221221
draggable: 'tr',
222-
onEnd: event => this.onEnd(event),
222+
onEnd: event => this.onEnd(),
223223
});
224224
} else {
225225
// stacked inline
@@ -228,12 +228,18 @@ class InlineSortable {
228228
animation: 150,
229229
handle: 'h3',
230230
draggable: '.inline-related.has_original',
231-
onEnd: event => this.onEnd(event),
231+
onEnd: event => this.onEnd(),
232232
});
233233
}
234+
inlineFieldSet.querySelectorAll('.inline-related .sort i.move-begin').forEach(
235+
elem => elem.addEventListener('click', event => this.move(event.target, 'begin'))
236+
);
237+
inlineFieldSet.querySelectorAll('.inline-related .sort i.move-end').forEach(
238+
elem => elem.addEventListener('click', event => this.move(event.target, 'end'))
239+
);
234240
}
235241

236-
private onEnd(evt: SortableEvent) {
242+
private onEnd() {
237243
const originals = this.sortable.el.querySelectorAll(this.itemSelectors);
238244
if (this.reversed) {
239245
originals.forEach((element: Element, index: number) => {
@@ -247,6 +253,23 @@ class InlineSortable {
247253
});
248254
}
249255
}
256+
257+
private move(target: EventTarget | null, direction: 'begin' | 'end') {
258+
if (!(target instanceof HTMLElement))
259+
return;
260+
const inlineRelated = target.closest(this.itemSelectors);
261+
if (!inlineRelated)
262+
return;
263+
const inlineRelatedList = this.sortable.el.querySelectorAll(this.itemSelectors);
264+
if (inlineRelatedList.length < 2)
265+
return;
266+
if (direction === 'begin') {
267+
inlineRelatedList[0].insertAdjacentElement('beforebegin', inlineRelated);
268+
} else {
269+
inlineRelatedList[inlineRelatedList.length - 1].insertAdjacentElement('afterend', inlineRelated);
270+
}
271+
this.onEnd();
272+
}
250273
}
251274

252275

testapp/admin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class AuthorAdmin(admin.ModelAdmin):
1515
class ChapterStackedInline(SortableInlineAdminMixin, admin.StackedInline):
1616
model = models.Chapter
1717
extra = 1
18+
template = 'adminsortable2/edit_inline/stacked.html'
1819

1920

2021
class ChapterStackedInlineReversed(SortableInlineAdminMixin, admin.StackedInline):
@@ -26,6 +27,7 @@ class ChapterStackedInlineReversed(SortableInlineAdminMixin, admin.StackedInline
2627
class ChapterTabularInline(SortableInlineAdminMixin, admin.TabularInline):
2728
model = models.Chapter
2829
extra = 1
30+
template = 'adminsortable2/edit_inline/tabular.html'
2931

3032

3133
class SortableBookAdmin(SortableAdminMixin, admin.ModelAdmin):

0 commit comments

Comments
 (0)