django-fast-treenode 2.1.4__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_fast_treenode-3.0.0.dist-info/METADATA +203 -0
- django_fast_treenode-3.0.0.dist-info/RECORD +90 -0
- {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info}/WHEEL +1 -1
- treenode/admin/__init__.py +2 -7
- treenode/admin/admin.py +138 -209
- treenode/admin/changelist.py +21 -39
- treenode/admin/exporter.py +170 -0
- treenode/admin/importer.py +171 -0
- treenode/admin/mixin.py +291 -0
- treenode/apps.py +42 -20
- treenode/cache.py +192 -303
- treenode/forms.py +45 -65
- treenode/managers/__init__.py +4 -20
- treenode/managers/managers.py +216 -0
- treenode/managers/queries.py +233 -0
- treenode/managers/tasks.py +167 -0
- treenode/models/__init__.py +8 -5
- treenode/models/decorators.py +54 -0
- treenode/models/factory.py +44 -68
- treenode/models/mixins/__init__.py +2 -1
- treenode/models/mixins/ancestors.py +44 -20
- treenode/models/mixins/children.py +33 -26
- treenode/models/mixins/descendants.py +33 -22
- treenode/models/mixins/family.py +25 -15
- treenode/models/mixins/logical.py +23 -21
- treenode/models/mixins/node.py +162 -104
- treenode/models/mixins/properties.py +22 -16
- treenode/models/mixins/roots.py +59 -15
- treenode/models/mixins/siblings.py +46 -43
- treenode/models/mixins/tree.py +212 -153
- treenode/models/mixins/update.py +154 -0
- treenode/models/models.py +365 -0
- treenode/settings.py +28 -0
- treenode/static/{treenode/css → css}/tree_widget.css +1 -1
- treenode/static/{treenode/css → css}/treenode_admin.css +43 -2
- treenode/static/css/treenode_tabs.css +51 -0
- treenode/static/js/lz-string.min.js +1 -0
- treenode/static/{treenode/js → js}/tree_widget.js +9 -23
- treenode/static/js/treenode_admin.js +531 -0
- treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -0
- treenode/static/vendors/jquery-ui/LICENSE.txt +43 -0
- treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_444444_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_555555_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_777620_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_777777_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/index.html +297 -0
- treenode/static/vendors/jquery-ui/jquery-ui.css +438 -0
- treenode/static/vendors/jquery-ui/jquery-ui.js +5223 -0
- treenode/static/vendors/jquery-ui/jquery-ui.min.css +7 -0
- treenode/static/vendors/jquery-ui/jquery-ui.min.js +6 -0
- treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -0
- treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +5 -0
- treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -0
- treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +5 -0
- treenode/static/vendors/jquery-ui/package.json +82 -0
- treenode/templates/admin/treenode_changelist.html +25 -0
- treenode/templates/admin/treenode_import_export.html +85 -0
- treenode/templates/admin/treenode_rows.html +57 -0
- treenode/tests.py +3 -0
- treenode/urls.py +6 -27
- treenode/utils/__init__.py +0 -15
- treenode/utils/db/__init__.py +7 -0
- treenode/utils/db/compiler.py +114 -0
- treenode/utils/db/db_vendor.py +50 -0
- treenode/utils/db/service.py +84 -0
- treenode/utils/db/sqlcompat.py +60 -0
- treenode/utils/db/sqlquery.py +70 -0
- treenode/version.py +2 -2
- treenode/views/__init__.py +5 -0
- treenode/views/autoapi.py +91 -0
- treenode/views/autocomplete.py +52 -0
- treenode/views/children.py +41 -0
- treenode/views/common.py +23 -0
- treenode/views/crud.py +209 -0
- treenode/views/search.py +48 -0
- treenode/widgets.py +27 -44
- django_fast_treenode-2.1.4.dist-info/METADATA +0 -166
- django_fast_treenode-2.1.4.dist-info/RECORD +0 -63
- treenode/admin/mixins.py +0 -302
- treenode/managers/adjacency.py +0 -205
- treenode/managers/closure.py +0 -278
- treenode/models/adjacency.py +0 -342
- treenode/models/classproperty.py +0 -27
- treenode/models/closure.py +0 -122
- treenode/static/treenode/js/.gitkeep +0 -1
- treenode/static/treenode/js/treenode_admin.js +0 -131
- treenode/templates/admin/export_success.html +0 -26
- treenode/templates/admin/tree_node_changelist.html +0 -19
- treenode/templates/admin/tree_node_export.html +0 -27
- treenode/templates/admin/tree_node_import.html +0 -45
- treenode/templates/admin/tree_node_import_report.html +0 -32
- treenode/templates/widgets/tree_widget.css +0 -23
- treenode/utils/aid.py +0 -46
- treenode/utils/base16.py +0 -38
- treenode/utils/base36.py +0 -37
- treenode/utils/db.py +0 -116
- treenode/utils/exporter.py +0 -196
- treenode/utils/importer.py +0 -328
- treenode/utils/radix.py +0 -61
- treenode/views.py +0 -184
- {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info/licenses}/LICENSE +0 -0
- {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info}/top_level.txt +0 -0
- /treenode/static/{treenode → css}/.gitkeep +0 -0
- /treenode/static/{treenode/css → js}/.gitkeep +0 -0
treenode/admin/admin.py
CHANGED
@@ -1,202 +1,190 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
"""
|
3
|
-
TreeNode Admin
|
3
|
+
TreeNode Admin Model Class
|
4
4
|
|
5
|
-
|
6
|
-
It includes custom tree-based sorting, optimized queries, and
|
7
|
-
import/export functionality for hierarchical data structures.
|
8
|
-
|
9
|
-
Version: 2.1.0
|
5
|
+
Version: 3.0.0
|
10
6
|
Author: Timur Kady
|
11
|
-
Email:
|
7
|
+
Email: timurkady@yandex.com
|
12
8
|
"""
|
13
9
|
|
14
10
|
|
15
|
-
import importlib
|
16
11
|
from django.contrib import admin
|
17
12
|
from django.db import models
|
18
13
|
from django.http import HttpResponseRedirect
|
14
|
+
from django.urls import reverse
|
19
15
|
from django.utils.safestring import mark_safe
|
20
|
-
from django.
|
16
|
+
from django.utils.translation import gettext_lazy as _
|
21
17
|
|
22
|
-
from .changelist import
|
23
|
-
from .
|
18
|
+
from .changelist import TreeNodeChangeList
|
19
|
+
from .mixin import AdminMixin
|
24
20
|
from ..forms import TreeNodeForm
|
25
21
|
from ..widgets import TreeWidget
|
22
|
+
from .importer import TreeNodeImporter
|
23
|
+
from .exporter import TreeNodeExporter
|
26
24
|
|
27
25
|
import logging
|
28
26
|
|
29
27
|
logger = logging.getLogger(__name__)
|
30
28
|
|
31
29
|
|
32
|
-
class
|
33
|
-
"""
|
34
|
-
TreeNodeAdmin class.
|
35
|
-
|
36
|
-
Admin configuration for TreeNodeModel with import/export functionality.
|
37
|
-
"""
|
30
|
+
class TreeNodeModelAdmin(AdminMixin, admin.ModelAdmin):
|
31
|
+
"""Admin for TreeNodeModel."""
|
38
32
|
|
33
|
+
# Режимы отображения
|
39
34
|
TREENODE_DISPLAY_MODE_ACCORDION = 'accordion'
|
40
35
|
TREENODE_DISPLAY_MODE_BREADCRUMBS = 'breadcrumbs'
|
41
36
|
TREENODE_DISPLAY_MODE_INDENTATION = 'indentation'
|
42
|
-
|
43
37
|
treenode_display_mode = TREENODE_DISPLAY_MODE_ACCORDION
|
44
|
-
|
45
|
-
|
38
|
+
|
39
|
+
form = TreeNodeForm
|
40
|
+
importer_class = None
|
41
|
+
exporter_class = None
|
46
42
|
ordering = []
|
47
43
|
list_per_page = 1000
|
48
|
-
list_sorting_mode_session_key = "treenode_sorting_mode"
|
49
44
|
|
50
|
-
form = TreeNodeForm
|
51
45
|
formfield_overrides = {
|
52
|
-
models.ForeignKey: {
|
46
|
+
models.ForeignKey: {'widget': TreeWidget()},
|
53
47
|
}
|
54
48
|
|
49
|
+
change_list_template = "admin/treenode_changelist.html"
|
50
|
+
|
55
51
|
class Media:
|
56
|
-
"""
|
52
|
+
"""Meta Class."""
|
57
53
|
|
58
54
|
css = {"all": (
|
59
|
-
"
|
55
|
+
"css/treenode_admin.css",
|
56
|
+
"vendors/jquery-ui/jquery-ui.css",
|
60
57
|
)}
|
61
58
|
js = (
|
62
|
-
|
63
|
-
|
59
|
+
"vendors/jquery-ui/jquery-ui.js",
|
60
|
+
# "js/lz-string.min.js",
|
61
|
+
"js/treenode_admin.js",
|
64
62
|
)
|
65
63
|
|
64
|
+
def __init__(self, model, admin_site):
|
65
|
+
"""Init method."""
|
66
|
+
super().__init__(model, admin_site)
|
67
|
+
|
68
|
+
if not self.list_display:
|
69
|
+
self.list_display = [field.name for field in model._meta.fields]
|
70
|
+
|
71
|
+
self.TreeNodeImporter = self.importer_class or TreeNodeImporter
|
72
|
+
self.TreeNodeExporter = self.exporter_class or TreeNodeExporter
|
73
|
+
|
66
74
|
def drag(self, obj):
|
67
|
-
"""
|
68
|
-
|
69
|
-
return mark_safe(f'<span class="treenode-drag-handle">{icon}</span>')
|
75
|
+
"""Drag and drop сolumn."""
|
76
|
+
return mark_safe('<span class="treenode-drag-handle">☰</span>')
|
70
77
|
|
71
|
-
drag.short_description = ""
|
78
|
+
drag.short_description = _("Move")
|
72
79
|
|
73
80
|
def toggle(self, obj):
|
74
|
-
"""
|
75
|
-
icon = "►" # ➕➖
|
81
|
+
"""Toggle column."""
|
76
82
|
if obj.get_children_count() > 0:
|
77
83
|
return mark_safe(
|
78
|
-
f'<button class="treenode-toggle" '
|
79
|
-
|
80
|
-
f'{icon}'
|
81
|
-
f'</button>')
|
84
|
+
f'<button class="treenode-toggle" data-node-id="{obj.pk}">►</button>' # noqa
|
85
|
+
)
|
82
86
|
return mark_safe('<div class="treenode-space"> </div>')
|
83
87
|
|
84
|
-
toggle.short_description = ""
|
88
|
+
toggle.short_description = _("Expand")
|
85
89
|
|
86
|
-
def
|
87
|
-
"""
|
88
|
-
|
90
|
+
def _get_treenode_field_display(self, request, obj):
|
91
|
+
"""Return HTML for the tree node in list view."""
|
92
|
+
level = obj.get_depth()
|
93
|
+
edit_url = reverse(
|
94
|
+
f"admin:{obj._meta.app_label}_{obj._meta.model_name}_change",
|
95
|
+
args=[obj.pk]
|
96
|
+
)
|
89
97
|
|
90
|
-
|
91
|
-
|
92
|
-
|
98
|
+
if self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_ACCORDION:
|
99
|
+
icon = "📄 " if obj.is_leaf() else "📁 "
|
100
|
+
content = (
|
101
|
+
f'<span style="padding-left: {level * 1.5}em;">'
|
102
|
+
f'{icon}<a href="{edit_url}">{str(obj)}</a></span>'
|
103
|
+
)
|
104
|
+
elif self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_BREADCRUMBS: # noqa
|
105
|
+
breadcrumbs = obj.get_breadcrumbs(
|
106
|
+
attr=getattr(obj, 'treenode_display_field', 'id'))
|
107
|
+
content = " / ".join(map(str, breadcrumbs))
|
108
|
+
elif self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_INDENTATION: # noqa
|
109
|
+
indent = "—" * level
|
110
|
+
content = f'{indent}<a href="{edit_url}">{str(obj)}</a>'
|
111
|
+
else:
|
112
|
+
content = f'<a href="{edit_url}">{str(obj)}</a>'
|
113
|
+
|
114
|
+
html = (
|
115
|
+
f'<div class="treenode-wrapper" '
|
116
|
+
f'data-treenode-pk="{obj.pk}" '
|
117
|
+
f'data-treenode-depth="{level}" '
|
118
|
+
f'data-treenode-parent="{obj.parent_id or ""}">'
|
119
|
+
f'<span class="treenode-content">{content}</span>'
|
120
|
+
f'</div>'
|
121
|
+
)
|
122
|
+
return mark_safe(html)
|
123
|
+
|
124
|
+
def get_list_display(self, request):
|
125
|
+
"""Generate list_display dynamically with tree-aware columns."""
|
126
|
+
# Define callable that replaces display field with tree field
|
127
|
+
def treenode_field(obj):
|
128
|
+
return self._get_treenode_field_display(request, obj)
|
129
|
+
treenode_field.short_description = self.model._meta.verbose_name
|
130
|
+
|
131
|
+
display_field = getattr(self.model, 'display_field', '__str__')
|
93
132
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
])
|
99
|
-
if not self.import_export:
|
100
|
-
check_results = [
|
101
|
-
pkg for pkg in ["openpyxl", "pyyaml", "xlsxwriter"]
|
102
|
-
if importlib.util.find_spec(pkg) is not None
|
103
|
-
]
|
104
|
-
logger.info("Packages" + ", ".join(check_results) + " are \
|
105
|
-
not installed. Export and import functions are disabled.")
|
106
|
-
|
107
|
-
if self.import_export:
|
108
|
-
from ..utils import TreeNodeImporter, TreeNodeExporter
|
109
|
-
|
110
|
-
self.TreeNodeImporter = TreeNodeImporter
|
111
|
-
self.TreeNodeExporter = TreeNodeExporter
|
133
|
+
user_list_display = list(super().get_list_display(request))
|
134
|
+
# If the list is empty or only contains __str__, replace it entirely
|
135
|
+
if not user_list_display or user_list_display == ['__str__']:
|
136
|
+
user_list_display = [treenode_field]
|
112
137
|
else:
|
113
|
-
|
114
|
-
|
138
|
+
try:
|
139
|
+
pos = user_list_display.index(display_field)
|
140
|
+
user_list_display.pop(pos)
|
141
|
+
user_list_display.insert(pos, treenode_field)
|
142
|
+
except ValueError:
|
143
|
+
user_list_display.insert(0, treenode_field)
|
115
144
|
|
116
|
-
|
117
|
-
|
118
|
-
|
145
|
+
return (self.drag, self.toggle) + tuple(user_list_display)
|
146
|
+
|
147
|
+
def get_list_display_links(self, request, list_display):
|
148
|
+
"""Get display list links."""
|
149
|
+
return ('treenode_field',)
|
119
150
|
|
120
|
-
|
121
|
-
|
122
|
-
return the full list.
|
123
|
-
"""
|
151
|
+
def get_queryset(self, request):
|
152
|
+
"""By default: only root nodes, unless searching or editing."""
|
124
153
|
qs = super().get_queryset(request)
|
125
154
|
|
126
|
-
|
127
|
-
app_label = self.model._meta.app_label
|
128
|
-
model_name = self.model._meta.model_name
|
129
|
-
if resolved_match.url_name == f"{app_label}_{model_name}_change":
|
155
|
+
if request.GET.get("q"):
|
130
156
|
return qs
|
131
157
|
|
132
|
-
|
133
|
-
|
158
|
+
resolved = request.resolver_match
|
159
|
+
if resolved and resolved.url_name.endswith("_change"):
|
160
|
+
return qs
|
134
161
|
|
135
|
-
|
136
|
-
q = request.GET.get("q", "")
|
137
|
-
if not field_name:
|
138
|
-
return qs.none()
|
139
|
-
return qs.select_related('tn_parent')\
|
140
|
-
.filter(**{f"{field_name}__icontains": q})
|
162
|
+
return qs.filter(parent__isnull=True)
|
141
163
|
|
142
164
|
def get_form(self, request, obj=None, **kwargs):
|
143
165
|
"""Get Form method."""
|
144
166
|
form = super().get_form(request, obj, **kwargs)
|
145
|
-
if "
|
146
|
-
form.base_fields["
|
167
|
+
if "parent" in form.base_fields:
|
168
|
+
form.base_fields["parent"].widget = TreeWidget()
|
147
169
|
return form
|
148
170
|
|
149
171
|
def get_search_fields(self, request):
|
150
|
-
"""
|
151
|
-
return [self.model
|
152
|
-
|
153
|
-
def get_list_display(self, request):
|
154
|
-
"""Generate list_display dynamically with user-defined preferences."""
|
155
|
-
change_view_cols = (self.drag, self.toggle)
|
156
|
-
user_list_display = list(super().get_list_display(request))
|
157
|
-
|
158
|
-
treenode_display_field = getattr(
|
159
|
-
self.model,
|
160
|
-
'treenode_display_field',
|
161
|
-
'__str__'
|
162
|
-
)
|
163
|
-
|
164
|
-
def treenode_field(obj):
|
165
|
-
return self._get_treenode_field_display(request, obj)
|
166
|
-
|
167
|
-
treenode_field.short_description = self.model._meta.verbose_name
|
172
|
+
"""Get search fields."""
|
173
|
+
return [getattr(self.model, 'treenode_display_field', 'id')]
|
168
174
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
else:
|
173
|
-
# Remove `treenode_display_field` if it is the first one and
|
174
|
-
# insert `treenode_field`
|
175
|
-
if user_list_display[0] == treenode_display_field:
|
176
|
-
clean_list = user_list_display[1:]
|
177
|
-
else:
|
178
|
-
clean_list = user_list_display
|
179
|
-
|
180
|
-
# Гарантируем, что treenode_field есть в списке
|
181
|
-
if treenode_field not in clean_list:
|
182
|
-
clean_list.insert(0, treenode_field)
|
183
|
-
|
184
|
-
result = tuple(clean_list)
|
175
|
+
def get_changelist(self, request, **kwargs):
|
176
|
+
"""Get ChangeList Class."""
|
177
|
+
return TreeNodeChangeList
|
185
178
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
"""Specify that only `treenode_field` should be clickable."""
|
190
|
-
return ('treenode_field',)
|
191
|
-
|
192
|
-
def get_changelist(self, request):
|
193
|
-
"""Use SortedChangeList to sort the results at render time."""
|
194
|
-
return SortedChangeList
|
179
|
+
def get_ordering(self, request):
|
180
|
+
"""Get ordering."""
|
181
|
+
return None
|
195
182
|
|
196
183
|
def changelist_view(self, request, extra_context=None):
|
197
184
|
"""Changelist View."""
|
198
185
|
extra_context = extra_context or {}
|
199
186
|
extra_context['import_export_enabled'] = self.import_export
|
187
|
+
extra_context['num_sorted_fields'] = len(self.get_ordering(request) or []) # noqa: D501
|
200
188
|
|
201
189
|
response = super().changelist_view(request, extra_context=extra_context)
|
202
190
|
|
@@ -205,91 +193,32 @@ not installed. Export and import functions are disabled.")
|
|
205
193
|
if isinstance(response, HttpResponseRedirect):
|
206
194
|
return response
|
207
195
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
196
|
+
ChangeListClass = self.get_changelist(request)
|
197
|
+
cl = ChangeListClass(
|
198
|
+
request,
|
199
|
+
self.model,
|
200
|
+
self.list_display,
|
201
|
+
self.list_display_links,
|
202
|
+
self.list_filter,
|
203
|
+
self.date_hierarchy,
|
204
|
+
self.search_fields,
|
205
|
+
self.list_select_related,
|
206
|
+
self.list_per_page,
|
207
|
+
self.list_max_show_all,
|
208
|
+
self.list_editable,
|
209
|
+
self,
|
210
|
+
self.get_sortable_by(request),
|
211
|
+
self.get_search_help_text(request),
|
212
|
+
)
|
222
213
|
|
223
|
-
|
224
|
-
|
214
|
+
cl.get_results(request)
|
215
|
+
cl.result_list = self.render_changelist_rows(cl.result_list, request)
|
225
216
|
|
226
217
|
return response
|
227
218
|
|
228
|
-
def
|
229
|
-
"""Get
|
230
|
-
return
|
231
|
-
|
232
|
-
# ------------------------------------------------------------------------
|
233
|
-
|
234
|
-
def _get_treenode_field_display(self, request, obj):
|
235
|
-
"""
|
236
|
-
Return the HTML display of the accordion node.
|
237
|
-
|
238
|
-
Modes:
|
239
|
-
- ACCORDION: ' ' * level + icon + str(node),
|
240
|
-
where icon = "📄" if obj.is_leaf() returns True, otherwise "📁".
|
241
|
-
- BREADCRUMBS: " / ".join(obj.get_breadcrumbs(attr=field)),
|
242
|
-
where field = getattr(self.model, 'treenode_display_field', None)
|
243
|
-
or "tn_priority" if None.
|
244
|
-
- INDENTATION: '—' * level + str(node)
|
245
|
-
"""
|
246
|
-
# Get a link to edit the object
|
247
|
-
meta = self.model._meta
|
248
|
-
edit_url = reverse(
|
249
|
-
f'admin:{meta.app_label}_{meta.model_name}_change', args=[obj.pk]
|
250
|
-
)
|
251
|
-
|
252
|
-
# Determine the node level
|
253
|
-
level = obj.get_depth()
|
254
|
-
|
255
|
-
mode = self.treenode_display_mode
|
256
|
-
if mode == self.TREENODE_DISPLAY_MODE_ACCORDION:
|
257
|
-
icon = "📄 " if obj.is_leaf() else "📁 "
|
258
|
-
obj_str = str(obj)
|
259
|
-
content = (
|
260
|
-
f'<span style="padding-left: {level * 1.5}em;">'
|
261
|
-
f'{icon}<a href="{edit_url}">{obj_str}</a>'
|
262
|
-
f'</span>'
|
263
|
-
)
|
264
|
-
elif mode == self.TREENODE_DISPLAY_MODE_BREADCRUMBS:
|
265
|
-
field = getattr(
|
266
|
-
self.model,
|
267
|
-
'treenode_display_field',
|
268
|
-
None) or "tn_priority"
|
269
|
-
content = " / ".join(obj.get_breadcrumbs(attr=field))
|
270
|
-
elif mode == self.TREENODE_DISPLAY_MODE_INDENTATION:
|
271
|
-
indent = "—" * level
|
272
|
-
obj_str = str(obj)
|
273
|
-
content = f'{indent}<a href="{edit_url}">{obj_str}</a>'
|
274
|
-
else:
|
275
|
-
# Just in case mode is not recognized, then use breadcrumbs
|
276
|
-
field = getattr(
|
277
|
-
self.model,
|
278
|
-
'treenode_display_field',
|
279
|
-
None) or "tn_priority"
|
280
|
-
content = " / ".join(obj.get_breadcrumbs(attr=field))
|
281
|
-
content = f'<a href="{edit_url}"">{content}</a>'
|
282
|
-
|
283
|
-
parent = str(getattr(obj, "tn_parent_id", "") or "")
|
284
|
-
html = (
|
285
|
-
f'<div class="treenode-wrapper" '
|
286
|
-
f'data-treenode-pk="{obj.pk}" '
|
287
|
-
f'data-treenode-depth="{level}" '
|
288
|
-
f'data-treenode-parent="{parent}">'
|
289
|
-
f'<span class="treenode-content">{content}</span>'
|
290
|
-
f'</div>'
|
291
|
-
)
|
292
|
-
return mark_safe(html)
|
219
|
+
def get_list_per_page(self, request):
|
220
|
+
"""Get list per page."""
|
221
|
+
return 999999
|
293
222
|
|
294
223
|
|
295
224
|
# The End
|
treenode/admin/changelist.py
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
"""
|
3
|
-
TreeNode Sorted ChangeList Class for
|
3
|
+
TreeNode Sorted ChangeList Class for TreeNodeModelAdmin.
|
4
4
|
|
5
|
-
Version:
|
5
|
+
Version: 3.0.0
|
6
6
|
Author: Timur Kady
|
7
|
-
Email:
|
7
|
+
Email: timurkady@yandex.com
|
8
8
|
"""
|
9
9
|
|
10
10
|
from django.contrib.admin.views.main import ChangeList
|
11
|
-
from django.
|
11
|
+
from django.forms.models import modelformset_factory
|
12
|
+
from django.db.models import Q
|
12
13
|
|
13
|
-
from ..cache import treenode_cache
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
class TreeNodeChangeList(ChangeList):
|
16
|
+
def __init__(self, *args, **kwargs):
|
17
|
+
super().__init__(*args, **kwargs)
|
18
18
|
|
19
19
|
def get_ordering(self, request, queryset):
|
20
20
|
"""
|
@@ -30,36 +30,18 @@ class SortedChangeList(ChangeList):
|
|
30
30
|
ordering.remove('-pk')
|
31
31
|
return tuple(ordering)
|
32
32
|
|
33
|
-
def get_queryset(self, request):
|
34
|
-
"""Get QuerySet with select_related."""
|
35
|
-
return super().get_queryset(request).select_related('tn_parent')
|
36
|
-
|
37
33
|
def get_results(self, request):
|
38
|
-
"""Return sorted results for ChangeList rendering."""
|
39
|
-
# Populate self.result_list with objects from the DB.
|
40
34
|
super().get_results(request)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
obj.object for obj in deserialize("json", json_str)
|
55
|
-
]
|
56
|
-
self.result_list = sorted_results
|
57
|
-
return
|
58
|
-
|
59
|
-
sorted_result = self.model._sort_node_list(result_list)
|
60
|
-
json_str = serialize("json", sorted_result)
|
61
|
-
treenode_cache.set(cache_key, json_str)
|
62
|
-
|
63
|
-
self.result_list = sorted_result
|
64
|
-
|
65
|
-
# The End
|
35
|
+
model_name = self.model._meta.model_name
|
36
|
+
|
37
|
+
# Добавляем атрибуты к результатам
|
38
|
+
object_ids = [r.pk for r in self.result_list]
|
39
|
+
objects_dict = {
|
40
|
+
obj.pk: obj
|
41
|
+
for obj in self.model_admin.model.objects.filter(pk__in=object_ids)
|
42
|
+
}
|
43
|
+
|
44
|
+
for result in self.result_list:
|
45
|
+
result.obj = objects_dict.get(result.pk)
|
46
|
+
# Добавляем атрибуты строк
|
47
|
+
result.row_attrs = f'data-node-id="{result.pk}" data-parent-of="{result.obj.parent_id or ""}" class="model-{model_name} pk-{result.pk}"'
|