Skip to content

Commit 2afebb7

Browse files
beryczboxed
authored andcommitted
reorderable EditTable finished with tests a docs
1 parent 782ce00 commit 2afebb7

6 files changed

Lines changed: 233 additions & 22 deletions

File tree

conftest.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ def damnation(transactional_db):
268268

269269
@pytest.fixture
270270
def fav_artists(john_doe_user, black_sabbath, damnation, ozzy):
271-
FavoriteArtist.objects.create(user=john_doe_user, artist=black_sabbath, comment='Love it!', sort_order=0)
272-
FavoriteArtist.objects.create(user=john_doe_user, artist=ozzy, comment='I love this too!', sort_order=1)
273-
FavoriteArtist.objects.create(user=john_doe_user, artist=damnation, comment='And this as well', sort_order=2)
271+
return [
272+
FavoriteArtist.objects.create(user=john_doe_user, artist=black_sabbath, comment='Love it!', sort_order=0),
273+
FavoriteArtist.objects.create(user=john_doe_user, artist=ozzy, comment='I love this too!', sort_order=1),
274+
FavoriteArtist.objects.create(user=john_doe_user, artist=damnation, comment='And this as well', sort_order=2),
275+
]

docs/test_doc_edit_tables.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,14 @@ def test_orderable_edit_tables(fav_artists):
6868
6969
.. code-block:: python
7070
71+
from iommi.models import Orderable
72+
7173
class FavoriteArtist(Orderable):
72-
user = ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='favorite_artists')
74+
user = ForeignKey(
75+
settings.AUTH_USER_MODEL,
76+
on_delete=models.CASCADE,
77+
related_name='favorite_artists',
78+
)
7379
artist = ForeignKey(Artist, on_delete=CASCADE, related_name='+')
7480
comment = CharField(max_length=255)
7581
@@ -88,6 +94,7 @@ class Meta:
8894
auto__model = FavoriteArtist
8995
auto__include = ['artist__name', 'comment', 'sort_order']
9096
columns__comment__field__include = True
97+
sortable = False
9198

9299
user = get_user_model().objects.get(username='john.doe')
93100

@@ -112,7 +119,7 @@ class Meta:
112119

113120
# language=rst
114121
"""
115-
If you already have a custom model field for ordering, you can register it as a reorderderin column:
122+
If you already have a custom model field for ordering, you can register it as a `reorder_handle` column:
116123
117124
.. code-block:: python
118125
@@ -131,6 +138,7 @@ class MyEditTable(EditTable):
131138
class Meta:
132139
auto__model = Foo
133140
columns__bar = EditColumn.reorder_handle()
141+
sortable = False
134142
"""
135143

136144
# language=rst
@@ -149,4 +157,11 @@ class Meta:
149157
"selectedClass": 'selected', # The class applied to the selected items
150158
"fallbackTolerance": 3, # So that we can select items on mobile
151159
}
160+
sortable = False
161+
"""
162+
163+
# language=rst
164+
"""
165+
Note: We recommend using `sortable=False` on orderable EditTables, because it doesn't really make sense to have both.
166+
And if you sort table by another column, the `reorder_handle` column gets `render_column=False`.
152167
"""

iommi/edit_table.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ def on_bind(self) -> None:
203203
super(EditColumn, self).on_bind()
204204
if 'reorder_handle' in getattr(self, 'iommi_shortcut_stack', []):
205205
edit_table = self.iommi_parent().iommi_parent()
206-
edit_table.tbody.attrs['data-reorderable-handle-selector'] = f'[data-iommi-path="{self.iommi_dunder_path}__cell"]'
207-
edit_table.tbody.attrs['data-reorderable-field-selector'] = '[data-reordering-value]'
206+
edit_table.tbody.attrs['data-iommi-reorderable-handle-selector'] = f'[data-iommi-path="{self.iommi_dunder_path}__cell"]'
207+
edit_table.tbody.attrs['data-iommi-reorderable-field-selector'] = '[data-reordering-value]'
208208

209209
@classmethod
210210
@with_defaults(
@@ -558,19 +558,32 @@ def bind(self, *, parent=None, request=None):
558558
return result
559559

560560
reorder_handles = []
561-
for column_name, column in items(result.columns):
561+
is_sorting_on = None
562+
for column in values(result.columns):
562563
if 'reorder_handle' in getattr(column, 'iommi_shortcut_stack', []):
563-
reorder_handles.append(column_name)
564+
reorder_handles.append(column)
565+
if column.is_sorting:
566+
is_sorting_on = column
567+
568+
# TODO when needed
569+
# instead of is_sorting_on we should rather check `result.rows.query.order_by != (reorder_handles.attr,)`
570+
# but query.order_by can be an empty tuple, or rows doesn't have to be a queryset etc.
571+
564572
if reorder_handles:
565573
assert len(reorder_handles) == 1, "You cannot have multiple EditColumn.reorder_handle in an EditTable!"
566-
if not result.reorderable:
574+
if is_sorting_on:
575+
# disable reordering when table is sorted by another column
576+
result.reorderable = False
577+
result.tbody.attrs['data-iommi-reorderable-handle-selector'] = None
578+
result.tbody.attrs['data-iommi-reorderable-field-selector'] = None
579+
reorder_handles[0].cell.attrs['class']['reordering-handle-cell'] = False
580+
reorder_handles[0].render_column = False
581+
result._bind_headers()
582+
elif not result.reorderable:
567583
result.reorderable = True
568-
result.sortable = False
569-
if result.reorderable:
570-
result.tbody.attrs['data-reorderable'] = json.dumps(result.reorderable) if isinstance(result.reorderable, dict) else result.reorderable
571584

572-
if result.sortable and result.reorderable:
573-
raise NotImplementedError("sortable and reorderable cannot be used simultaneously")
585+
if result.reorderable:
586+
result.tbody.attrs['data-iommi-reorderable'] = json.dumps(result.reorderable, separators=(',', ':')) if isinstance(result.reorderable, dict) else result.reorderable
574587

575588
return result
576589

iommi/edit_table__tests.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import html
12
import json
23
import pytest
34

45
from docs.models import (
56
Album,
67
Artist,
8+
FavoriteArtist,
79
)
810
from iommi.declarative.namespace import Namespace
911
from iommi.edit_table import (
@@ -840,3 +842,181 @@ def test_parent_form_save(abort_on_fail):
840842
</form>
841843
""",
842844
)
845+
846+
847+
@pytest.mark.django_db
848+
def test_orderable_edit_table(fav_artists):
849+
class FavoriteArtists(EditTable):
850+
class Meta:
851+
auto__model = FavoriteArtist
852+
auto__include = ['artist__name', 'comment', 'sort_order']
853+
columns__comment__field__include = True
854+
sortable = False
855+
856+
verify_table_html(
857+
table=FavoriteArtists().bind(request=req('get')),
858+
# language=html
859+
expected_html=f"""
860+
<table class="table" data-new-row-endpoint="/new_row" data-next-virtual-pk="-1">
861+
<thead>
862+
<tr>
863+
<th class="first_column subheader">Name</th>
864+
<th class="first_column subheader">Comment</th>
865+
<th class="first_column subheader">Sort order</th>
866+
</tr>
867+
</thead>
868+
<tbody data-iommi-reorderable data-iommi-reorderable-field-selector="[data-reordering-value]" data-iommi-reorderable-handle-selector="[data-iommi-path=&quot;columns__sort_order__cell&quot;]">
869+
<tr data-pk="{fav_artists[0].pk}">
870+
<td>Black Sabbath</td>
871+
<td><input id="id_columns__comment__{fav_artists[0].pk}" name="columns/comment/{fav_artists[0].pk}" type="text" value="Love it!"></td>
872+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__sort_order__{fav_artists[0].pk}" name="columns/sort_order/{fav_artists[0].pk}" type="hidden" value="0"></td>
873+
</tr>
874+
<tr data-pk="{fav_artists[1].pk}">
875+
<td>Ozzy Osbourne</td>
876+
<td><input id="id_columns__comment__{fav_artists[1].pk}" name="columns/comment/{fav_artists[1].pk}" type="text" value="I love this too!"></td>
877+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__sort_order__{fav_artists[1].pk}" name="columns/sort_order/{fav_artists[1].pk}" type="hidden" value="1"></td>
878+
</tr>
879+
<tr data-pk="{fav_artists[2].pk}">
880+
<td>Damnation</td>
881+
<td><input id="id_columns__comment__{fav_artists[2].pk}" name="columns/comment/{fav_artists[2].pk}" type="text" value="And this as well"></td>
882+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__sort_order__{fav_artists[2].pk}" name="columns/sort_order/{fav_artists[2].pk}" type="hidden" value="2"></td>
883+
</tr>
884+
</tbody>
885+
</table>
886+
""",
887+
)
888+
889+
890+
@pytest.mark.django_db
891+
def test_orderable_edit_table_sortablejs_options(fav_artists):
892+
sortablejs_options = {
893+
"multiDrag": True, # Enable multi-drag
894+
"selectedClass": 'selected', # The class applied to the selected items
895+
"fallbackTolerance": 3, # So that we can select items on mobile
896+
}
897+
class FavoriteArtists(EditTable):
898+
class Meta:
899+
auto__model = FavoriteArtist
900+
auto__include = ['artist__name', 'comment', 'sort_order']
901+
columns__comment__field__include = True
902+
reorderable = sortablejs_options
903+
sortable = False
904+
905+
verify_table_html(
906+
table=FavoriteArtists().bind(request=req('get')),
907+
# language=html
908+
expected_html=f"""
909+
<table class="table" data-new-row-endpoint="/new_row" data-next-virtual-pk="-1">
910+
<thead>
911+
<tr>
912+
<th class="first_column subheader">Name</th>
913+
<th class="first_column subheader">Comment</th>
914+
<th class="first_column subheader">Sort order</th>
915+
</tr>
916+
</thead>
917+
<tbody data-iommi-reorderable="{html.escape(json.dumps(sortablejs_options, separators=(',', ':')))}" data-iommi-reorderable-field-selector="[data-reordering-value]" data-iommi-reorderable-handle-selector="[data-iommi-path=&quot;columns__sort_order__cell&quot;]">
918+
<tr data-pk="{fav_artists[0].pk}">
919+
<td>Black Sabbath</td>
920+
<td><input id="id_columns__comment__{fav_artists[0].pk}" name="columns/comment/{fav_artists[0].pk}" type="text" value="Love it!"></td>
921+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__sort_order__{fav_artists[0].pk}" name="columns/sort_order/{fav_artists[0].pk}" type="hidden" value="0"></td>
922+
</tr>
923+
<tr data-pk="{fav_artists[1].pk}">
924+
<td>Ozzy Osbourne</td>
925+
<td><input id="id_columns__comment__{fav_artists[1].pk}" name="columns/comment/{fav_artists[1].pk}" type="text" value="I love this too!"></td>
926+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__sort_order__{fav_artists[1].pk}" name="columns/sort_order/{fav_artists[1].pk}" type="hidden" value="1"></td>
927+
</tr>
928+
<tr data-pk="{fav_artists[2].pk}">
929+
<td>Damnation</td>
930+
<td><input id="id_columns__comment__{fav_artists[2].pk}" name="columns/comment/{fav_artists[2].pk}" type="text" value="And this as well"></td>
931+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__sort_order__{fav_artists[2].pk}" name="columns/sort_order/{fav_artists[2].pk}" type="hidden" value="2"></td>
932+
</tr>
933+
</tbody>
934+
</table>
935+
""",
936+
)
937+
938+
939+
@pytest.mark.django_db
940+
def test_orderable_edit_table_reorder_handle():
941+
obj1 = TFoo.objects.create(a=1, b='test_reorder_handle')
942+
obj2 = TFoo.objects.create(a=2, b='test_reorder_handle')
943+
944+
class MyEditTable(EditTable):
945+
class Meta:
946+
auto__model = TFoo
947+
rows = TFoo.objects.filter(b='test_reorder_handle')
948+
auto__include = ['a', 'b']
949+
columns__a = EditColumn.reorder_handle()
950+
sortable = False
951+
952+
verify_table_html(
953+
table=MyEditTable().bind(request=req('get')),
954+
# language=html
955+
expected_html=f"""
956+
<table class="table" data-new-row-endpoint="/new_row" data-next-virtual-pk="-1">
957+
<thead>
958+
<tr>
959+
<th class="first_column subheader">B</th>
960+
<th class="first_column subheader">A</th>
961+
</tr>
962+
</thead>
963+
<tbody data-iommi-reorderable data-iommi-reorderable-field-selector="[data-reordering-value]" data-iommi-reorderable-handle-selector="[data-iommi-path=&quot;columns__a__cell&quot;]">
964+
<tr data-pk="{obj1.pk}">
965+
<td>test_reorder_handle</td>
966+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__a__{obj1.pk}" name="columns/a/{obj1.pk}" type="hidden" value="1"></td>
967+
</tr>
968+
<tr data-pk="{obj2.pk}">
969+
<td>test_reorder_handle</td>
970+
<td class="reordering-handle-cell" title="Drag and drop to reorder"><input data-reordering-value id="id_columns__a__{obj2.pk}" name="columns/a/{obj2.pk}" type="hidden" value="2"></td>
971+
</tr>
972+
</tbody>
973+
</table>
974+
""",
975+
)
976+
977+
978+
@pytest.mark.django_db
979+
def test_orderable_sortable_edit_table(fav_artists):
980+
"""
981+
when sorted by any column, the reordering turns off
982+
"""
983+
class FavoriteArtists(EditTable):
984+
class Meta:
985+
auto__model = FavoriteArtist
986+
auto__include = ['artist__name', 'comment', 'sort_order']
987+
columns__comment__field__include = True
988+
989+
fav_artists_data = FavoriteArtist.objects.order_by('artist__name')
990+
991+
verify_table_html(
992+
table=FavoriteArtists().bind(request=req('get', order='artist_name')),
993+
# language=html
994+
expected_html=f"""
995+
<table class="table" data-new-row-endpoint="/new_row" data-next-virtual-pk="-1">
996+
<thead>
997+
<tr>
998+
<th class="ascending first_column iommi_sort_header sorted subheader">
999+
<a href="?order=-artist_name">Name</a>
1000+
</th>
1001+
<th class="first_column iommi_sort_header subheader">
1002+
<a href="?order=comment">Comment</a>
1003+
</th>
1004+
</tr>
1005+
</thead>
1006+
<tbody>
1007+
<tr data-pk="{fav_artists_data[0].pk}">
1008+
<td>{fav_artists_data[0].artist.name}</td>
1009+
<td><input id="id_columns__comment__{fav_artists_data[0].pk}" name="columns/comment/{fav_artists_data[0].pk}" type="text" value="{fav_artists_data[0].comment}"></td>
1010+
</tr>
1011+
<tr data-pk="{fav_artists_data[1].pk}">
1012+
<td>{fav_artists_data[1].artist.name}</td>
1013+
<td><input id="id_columns__comment__{fav_artists_data[1].pk}" name="columns/comment/{fav_artists_data[1].pk}" type="text" value="{fav_artists_data[1].comment}"></td>
1014+
</tr>
1015+
<tr data-pk="{fav_artists_data[2].pk}">
1016+
<td>{fav_artists_data[2].artist.name}</td>
1017+
<td><input id="id_columns__comment__{fav_artists_data[2].pk}" name="columns/comment/{fav_artists_data[2].pk}" type="text" value="{fav_artists_data[2].comment}"></td>
1018+
</tr>
1019+
</tbody>
1020+
</table>
1021+
""",
1022+
)

iommi/static/css/iommi.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ div[data-iommi-edit-table-delete-with="checkbox"] input[data-iommi-edit-table-de
5959
cursor: move;
6060
cursor: -webkit-grabbing;
6161
width: 30px;
62+
text-align: right;
6263
}
6364
.reordering-handle-cell::after {
6465
content: "||";

iommi/static/js/iommi.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ class IommiSelect2 {
605605
}
606606

607607
class IommiReorderable {
608-
defaultSelector = '[data-reorderable]';
608+
defaultSelector = '[data-iommi-reorderable]';
609609

610610
constructor() {
611611
const SELF = this;
@@ -628,7 +628,7 @@ class IommiReorderable {
628628
recalculate(tbody) {
629629
let index = 0;
630630
for (let item of tbody.children) {
631-
item.querySelector(tbody.dataset.reorderableFieldSelector).value = index;
631+
item.querySelector(tbody.dataset.iommiReorderableFieldSelector).value = index;
632632
index += 1;
633633
}
634634
}
@@ -647,15 +647,15 @@ class IommiReorderable {
647647
const options = {
648648
animation: 150
649649
};
650-
if(elem.dataset.reorderableHandleSelector) {
651-
options.handle = elem.dataset.reorderableHandleSelector;
650+
if(elem.dataset.iommiReorderableHandleSelector) {
651+
options.handle = elem.dataset.iommiReorderableHandleSelector;
652652
}
653653
let requiredOptions = {}
654-
if(elem.dataset.reorderable.startsWith("{")) {
655-
requiredOptions = JSON.parse(elem.dataset.reorderable);
654+
if(elem.dataset.iommiReorderable.startsWith("{")) {
655+
requiredOptions = JSON.parse(elem.dataset.iommiReorderable);
656656
}
657657
const SELF = this;
658-
if(elem.dataset.reorderableFieldSelector) {
658+
if(elem.dataset.iommiReorderableFieldSelector) {
659659
options.onUpdate = function(event) {
660660
SELF.recalculate(elem);
661661
}

0 commit comments

Comments
 (0)