django-fast-treenode 1.1.3__py3-none-any.whl → 2.0.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/METADATA +127 -46
- django_fast_treenode-2.0.1.dist-info/RECORD +41 -0
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/WHEEL +1 -1
- treenode/__init__.py +0 -7
- treenode/admin.py +327 -82
- treenode/apps.py +20 -3
- treenode/cache.py +231 -0
- treenode/docs/Documentation +101 -54
- treenode/forms.py +75 -19
- treenode/managers.py +260 -48
- treenode/models/__init__.py +7 -0
- treenode/models/classproperty.py +24 -0
- treenode/models/closure.py +168 -0
- treenode/models/factory.py +71 -0
- treenode/models/proxy.py +650 -0
- treenode/static/treenode/css/tree_widget.css +62 -0
- treenode/static/treenode/css/treenode_admin.css +106 -0
- treenode/static/treenode/js/tree_widget.js +161 -0
- treenode/static/treenode/js/treenode_admin.js +171 -0
- treenode/templates/admin/export_success.html +26 -0
- treenode/templates/admin/tree_node_changelist.html +11 -0
- treenode/templates/admin/tree_node_export.html +27 -0
- treenode/templates/admin/tree_node_import.html +27 -0
- treenode/templates/widgets/tree_widget.css +23 -0
- treenode/templates/widgets/tree_widget.html +21 -0
- treenode/urls.py +34 -0
- treenode/utils/__init__.py +4 -0
- treenode/utils/base36.py +35 -0
- treenode/utils/exporter.py +141 -0
- treenode/utils/importer.py +296 -0
- treenode/version.py +11 -1
- treenode/views.py +102 -2
- treenode/widgets.py +49 -27
- django_fast_treenode-1.1.3.dist-info/RECORD +0 -33
- treenode/compat.py +0 -8
- treenode/factory.py +0 -68
- treenode/models.py +0 -668
- treenode/static/select2tree/.gitkeep +0 -1
- treenode/static/select2tree/select2tree.css +0 -176
- treenode/static/select2tree/select2tree.js +0 -181
- treenode/static/treenode/css/treenode.css +0 -85
- treenode/static/treenode/js/treenode.js +0 -201
- treenode/templates/widgets/.gitkeep +0 -1
- treenode/templates/widgets/attrs.html +0 -7
- treenode/templates/widgets/options.html +0 -1
- treenode/templates/widgets/select2tree.html +0 -22
- treenode/tests.py +0 -3
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/LICENSE +0 -0
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/top_level.txt +0 -0
- /treenode/{docs → templates/admin}/.gitkeep +0 -0
treenode/models.py
DELETED
@@ -1,668 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
TreeNode Models Module
|
4
|
-
|
5
|
-
"""
|
6
|
-
|
7
|
-
|
8
|
-
from django.db import models
|
9
|
-
from django.db import transaction
|
10
|
-
from django.core.cache import caches
|
11
|
-
from django.utils.translation import gettext_lazy as _
|
12
|
-
from six import with_metaclass
|
13
|
-
from . import classproperty
|
14
|
-
from .compat import force_str
|
15
|
-
from .factory import TreeFactory
|
16
|
-
from .managers import TreeNodeManager
|
17
|
-
|
18
|
-
|
19
|
-
treenode_cache = caches['treenode']
|
20
|
-
|
21
|
-
def cached_tree_method(func):
|
22
|
-
"""
|
23
|
-
Decorator to cache the results of tree methods
|
24
|
-
|
25
|
-
The decorator caches the results of the decorated method using the
|
26
|
-
closure_path of the node as part of the cache key. If the cache is
|
27
|
-
cleared or invalidated, the cached results will be recomputed.
|
28
|
-
|
29
|
-
Usage:
|
30
|
-
@cached_tree_method
|
31
|
-
def my_tree_method(self):
|
32
|
-
# Tree method logic
|
33
|
-
"""
|
34
|
-
|
35
|
-
def wrapper(self, *args, **kwargs):
|
36
|
-
cache_key = f"{self.__class__.__name__}_{self.pk}_tree_{func.__name__}"
|
37
|
-
result = treenode_cache.get(cache_key)
|
38
|
-
|
39
|
-
if result is None:
|
40
|
-
result = func(self, *args, **kwargs)
|
41
|
-
treenode_cache.set(cache_key, result)
|
42
|
-
|
43
|
-
return result
|
44
|
-
|
45
|
-
return wrapper
|
46
|
-
|
47
|
-
|
48
|
-
class TreeNodeModel(with_metaclass(TreeFactory, models.Model)):
|
49
|
-
|
50
|
-
treenode_display_field = None
|
51
|
-
|
52
|
-
tn_parent = models.ForeignKey(
|
53
|
-
'self',
|
54
|
-
related_name='tn_children',
|
55
|
-
on_delete=models.CASCADE,
|
56
|
-
null=True,
|
57
|
-
blank=True,
|
58
|
-
verbose_name=_('Parent'),
|
59
|
-
)
|
60
|
-
|
61
|
-
tn_priority = models.PositiveIntegerField(
|
62
|
-
default=0,
|
63
|
-
verbose_name=_('Priority'),
|
64
|
-
)
|
65
|
-
|
66
|
-
objects = TreeNodeManager()
|
67
|
-
|
68
|
-
class Meta:
|
69
|
-
abstract = True
|
70
|
-
|
71
|
-
def __str__(self):
|
72
|
-
if self.treenode_display_field:
|
73
|
-
return str(getattr(self, self.treenode_display_field))
|
74
|
-
else:
|
75
|
-
return 'Node %d' % self.pk
|
76
|
-
|
77
|
-
# Public methods
|
78
|
-
|
79
|
-
@classmethod
|
80
|
-
def get_roots(cls):
|
81
|
-
"""Get a list with all root nodes"""
|
82
|
-
return list(item for item in cls.get_roots_queryset())
|
83
|
-
|
84
|
-
@classmethod
|
85
|
-
@cached_tree_method
|
86
|
-
def get_roots_queryset(cls):
|
87
|
-
"""Get root nodes queryset"""
|
88
|
-
return cls.objects.filter(tn_parent=None)
|
89
|
-
|
90
|
-
@classmethod
|
91
|
-
@cached_tree_method
|
92
|
-
def get_tree(cls, instance=None):
|
93
|
-
"""Get a n-dimensional dict representing the model tree"""
|
94
|
-
|
95
|
-
objs_list = list(instance,) if instance else cls.get_roots()
|
96
|
-
objs_tree = list()
|
97
|
-
for item in objs_list:
|
98
|
-
objs_tree.append(item.object2dict(item, []))
|
99
|
-
return objs_tree
|
100
|
-
|
101
|
-
@classmethod
|
102
|
-
@cached_tree_method
|
103
|
-
def get_tree_display(cls, cache=True):
|
104
|
-
"""Get a multiline string representing the model tree"""
|
105
|
-
|
106
|
-
objs = list(cls.objects.all())
|
107
|
-
return '\n'.join(['%s' % (obj,) for obj in objs])
|
108
|
-
|
109
|
-
@classmethod
|
110
|
-
def update_tree(cls):
|
111
|
-
"""Update tree manually, useful after bulk updates"""
|
112
|
-
|
113
|
-
treenode_cache.clear()
|
114
|
-
|
115
|
-
cls.closure_model.objects.all().delete()
|
116
|
-
|
117
|
-
# Apparently later, you should think about using iterators to reduce
|
118
|
-
# the amount of memory used.
|
119
|
-
# I'm sorry, I don't have time for that right now.
|
120
|
-
|
121
|
-
cls.closure_model.objects.bulk_create([
|
122
|
-
cls.closure_model(
|
123
|
-
parent_id=item.pk,
|
124
|
-
child_id=item.pk,
|
125
|
-
depth=0
|
126
|
-
)
|
127
|
-
for item in cls.objects.all()
|
128
|
-
])
|
129
|
-
|
130
|
-
for node in cls.objects.all():
|
131
|
-
queryset = cls.closure_model.objects.all()
|
132
|
-
parents = queryset.filter(
|
133
|
-
child=node.parent).values('parent', 'depth')
|
134
|
-
children = queryset.filter(
|
135
|
-
parent=node).values('child', 'depth')
|
136
|
-
objects = [
|
137
|
-
cls.closure_model(
|
138
|
-
parent_id=p['parent'],
|
139
|
-
child_id=c['child'],
|
140
|
-
depth=p['depth'] + c['depth'] + 1
|
141
|
-
)
|
142
|
-
for p in parents
|
143
|
-
for c in children
|
144
|
-
]
|
145
|
-
cls.closure_model.objects.bulk_create(objects)
|
146
|
-
|
147
|
-
@classmethod
|
148
|
-
def delete_tree(cls):
|
149
|
-
"""Delete the whole tree for the current node class"""
|
150
|
-
treenode_cache.clear()
|
151
|
-
cls.closure_model.objects.all().delete()
|
152
|
-
cls.objects.all().delete()
|
153
|
-
|
154
|
-
def get_ancestors(self, include_self=True, depth=None):
|
155
|
-
"""Get a list with all ancestors (ordered from root to self/parent)"""
|
156
|
-
|
157
|
-
options = dict(child_id=self.pk, depth__gte=0 if include_self else 1)
|
158
|
-
if depth:
|
159
|
-
options.update({'depth__lte': depth})
|
160
|
-
|
161
|
-
qs = self._closure_model.objects.filter(**options).order_by('-depth')
|
162
|
-
|
163
|
-
return list(item.parent for item in qs)
|
164
|
-
|
165
|
-
def get_ancestors_count(self, include_self=True, depth=None):
|
166
|
-
"""Get the ancestors count"""
|
167
|
-
|
168
|
-
return self.get_ancestors_queryset(include_self, depth).count()
|
169
|
-
|
170
|
-
def get_ancestors_pks(self, include_self=True, depth=None):
|
171
|
-
"""Get the ancestors pks list"""
|
172
|
-
|
173
|
-
options = dict(child_id=self.pk, depth__gte=0 if include_self else 1)
|
174
|
-
if depth:
|
175
|
-
options.update({'depth__lte': depth})
|
176
|
-
|
177
|
-
qs = self._closure_model.objects.filter(**options).order_by('-depth')
|
178
|
-
|
179
|
-
return list(item.parent.pk for item in qs)
|
180
|
-
|
181
|
-
@cached_tree_method
|
182
|
-
def get_ancestors_queryset(self, include_self=True, depth=None):
|
183
|
-
"""Get the ancestors queryset (self, ordered from parent to root)"""
|
184
|
-
|
185
|
-
options = dict(child_id=self.pk, depth__gte=0 if include_self else 1)
|
186
|
-
if depth:
|
187
|
-
options.update({'depth__lte': depth})
|
188
|
-
|
189
|
-
qs = self._closure_model.objects.filter(**options).order_by('-depth')
|
190
|
-
select = list(item.parent.pk for item in qs)
|
191
|
-
result = self._meta.model.objects.filter(pk__in=select)
|
192
|
-
return result
|
193
|
-
|
194
|
-
@cached_tree_method
|
195
|
-
def get_breadcrumbs(self, attr=None):
|
196
|
-
"""Get the breadcrumbs to current node (self, included)"""
|
197
|
-
|
198
|
-
qs = self._closure_model.objects.filter(child=self).order_by('-depth')
|
199
|
-
if attr:
|
200
|
-
return list(getattr(item.parent, attr) for item in qs)
|
201
|
-
else:
|
202
|
-
return list(item.parent for item in qs)
|
203
|
-
|
204
|
-
def get_children(self):
|
205
|
-
"""Get a list containing all children"""
|
206
|
-
|
207
|
-
return list(self.self.get_children_queryset())
|
208
|
-
|
209
|
-
def get_children_count(self):
|
210
|
-
"""Get the children count"""
|
211
|
-
|
212
|
-
return self.get_children_queryset().count()
|
213
|
-
|
214
|
-
def get_children_pks(self):
|
215
|
-
"""Get the children pks list"""
|
216
|
-
|
217
|
-
return [ch.pk for ch in self.get_children_queryset()]
|
218
|
-
|
219
|
-
@cached_tree_method
|
220
|
-
def get_children_queryset(self):
|
221
|
-
"""Get the children queryset"""
|
222
|
-
return self._meta.model.objects.filter(tn_parent=self.id)
|
223
|
-
|
224
|
-
def get_depth(self):
|
225
|
-
"""Get the node depth (self, how many levels of descendants)"""
|
226
|
-
|
227
|
-
depths = self._closure_model.objects.filter(parent=self).values_list(
|
228
|
-
'depth',
|
229
|
-
flat=True
|
230
|
-
)
|
231
|
-
return max(depths)
|
232
|
-
|
233
|
-
def get_descendants(self, include_self=False, depth=None):
|
234
|
-
"""Get a list containing all descendants"""
|
235
|
-
return list(self.get_descendants_queryset(include_self, depth))
|
236
|
-
|
237
|
-
def get_descendants_count(self, include_self=False, depth=None):
|
238
|
-
"""Get the descendants count"""
|
239
|
-
return self.get_descendants_queryset().count()
|
240
|
-
|
241
|
-
def get_descendants_pks(self, include_self=False, depth=None):
|
242
|
-
"""Get the descendants pks list"""
|
243
|
-
options = dict(parent_id=self.pk, depth__gte=0 if include_self else 1)
|
244
|
-
if depth:
|
245
|
-
options.update({'depth__lte': depth})
|
246
|
-
|
247
|
-
qs = self._closure_model.objects.filter(**options)
|
248
|
-
return [ch.child.pk for ch in qs] if qs else []
|
249
|
-
|
250
|
-
@cached_tree_method
|
251
|
-
def get_descendants_queryset(self, include_self=False, depth=None):
|
252
|
-
"""Get the descendants queryset"""
|
253
|
-
|
254
|
-
pks = self.get_descendants_pks(include_self, depth)
|
255
|
-
return self._meta.model.objects.filter(pk__in=pks)
|
256
|
-
|
257
|
-
def get_descendants_tree(self):
|
258
|
-
"""Get a n-dimensional dict representing the model tree"""
|
259
|
-
|
260
|
-
return self._meta.model.get_tree(instance=self)
|
261
|
-
|
262
|
-
def get_descendants_tree_display(self, include_self=False, depth=None):
|
263
|
-
"""Get a multiline string representing the model tree"""
|
264
|
-
|
265
|
-
objs = self.get_descendants()
|
266
|
-
return '\n'.join(['%s' % (obj,) for obj in objs])
|
267
|
-
|
268
|
-
def get_first_child(self):
|
269
|
-
"""Get the first child node"""
|
270
|
-
return self.get_children_queryset().first()
|
271
|
-
|
272
|
-
def get_index(self):
|
273
|
-
"""Get the node index (self, index in node.parent.children list)"""
|
274
|
-
source = self.get_siblings()
|
275
|
-
return source.index(self)
|
276
|
-
|
277
|
-
def get_last_child(self):
|
278
|
-
"""Get the last child node"""
|
279
|
-
return self.get_children_queryset().last()
|
280
|
-
|
281
|
-
def get_level(self):
|
282
|
-
"""Get the node level (self, starting from 1)"""
|
283
|
-
|
284
|
-
levels = self._closure_model.objects.filter(child=self).values_list(
|
285
|
-
'depth',
|
286
|
-
flat=True
|
287
|
-
)
|
288
|
-
return max(levels) + 1
|
289
|
-
|
290
|
-
def get_path(self, prefix='', suffix='', delimiter='.', format_str=''):
|
291
|
-
"""Return Materialized Path of node"""
|
292
|
-
|
293
|
-
str_ = '{%s}' % format_str
|
294
|
-
return prefix + delimiter.join(
|
295
|
-
str_.format(i) for i in self.get_breadcrumbs(attr='tn_priority')
|
296
|
-
) + suffix
|
297
|
-
|
298
|
-
def get_parent(self):
|
299
|
-
"""Get the parent node"""
|
300
|
-
return self.tn_parent
|
301
|
-
|
302
|
-
def get_parent_pk(self):
|
303
|
-
"""Get the parent node pk"""
|
304
|
-
return self.tn_parent.pk if self.tn_parent else None
|
305
|
-
|
306
|
-
def set_parent(self, parent_obj):
|
307
|
-
"""Set the parent node"""
|
308
|
-
self.tn_parent = parent_obj
|
309
|
-
|
310
|
-
def get_priority(self):
|
311
|
-
return self.tn_priority
|
312
|
-
|
313
|
-
def set_priority(self, priority=0):
|
314
|
-
"""Set the node priority"""
|
315
|
-
self.tn_priority = priority
|
316
|
-
|
317
|
-
def get_root(self):
|
318
|
-
"""Get the root node for the current node"""
|
319
|
-
qs = self._closure_model.objects.filter(child=self).order_by('depth')
|
320
|
-
return qs.last().parent if qs.count() > 0 else None
|
321
|
-
|
322
|
-
def get_root_pk(self):
|
323
|
-
"""Get the root node pk for the current node"""
|
324
|
-
self.get_root().pk
|
325
|
-
|
326
|
-
def get_siblings(self):
|
327
|
-
"""Get a list with all the siblings"""
|
328
|
-
return list(self.get_siblings_queryset())
|
329
|
-
|
330
|
-
def get_siblings_count(self):
|
331
|
-
"""Get the siblings count"""
|
332
|
-
return self.get_siblings_queryset().count()
|
333
|
-
|
334
|
-
def get_siblings_pks(self):
|
335
|
-
"""Get the siblings pks list"""
|
336
|
-
return [item.pk for item in self.get_siblings_queryset()]
|
337
|
-
|
338
|
-
@cached_tree_method
|
339
|
-
def get_siblings_queryset(self):
|
340
|
-
"""Get the siblings queryset"""
|
341
|
-
if self.tn_parent:
|
342
|
-
queryset = self.tn_parent.tn_children.all()
|
343
|
-
else:
|
344
|
-
queryset = self._meta.model.objects.filter(tn_parent__isnull=True)
|
345
|
-
return queryset.exclude(pk=self.pk)
|
346
|
-
|
347
|
-
def is_ancestor_of(self, target_obj):
|
348
|
-
"""Return True if the current node is ancestor of target_obj"""
|
349
|
-
return self.pk in target_obj.get_ancestors_pks()
|
350
|
-
|
351
|
-
def is_child_of(self, target_obj):
|
352
|
-
"""Return True if the current node is child of target_obj"""
|
353
|
-
|
354
|
-
return self.pk in target_obj.get_children_pks()
|
355
|
-
|
356
|
-
def is_descendant_of(self, target_obj):
|
357
|
-
"""Return True if the current node is descendant of target_obj"""
|
358
|
-
return self.pk in target_obj.get_descendants_pks()
|
359
|
-
|
360
|
-
def is_first_child(self):
|
361
|
-
"""Return True if the current node is the first child"""
|
362
|
-
return self.tn_priority == 0
|
363
|
-
|
364
|
-
def is_last_child(self):
|
365
|
-
"""Return True if the current node is the last child"""
|
366
|
-
|
367
|
-
return self.tn_priority == self.get_siblings_count() - 1
|
368
|
-
|
369
|
-
def is_leaf(self):
|
370
|
-
"""Return True if the current node is leaf (self, it has not children)"""
|
371
|
-
return self.tn_children.count() == 0
|
372
|
-
|
373
|
-
def is_parent_of(self, target_obj):
|
374
|
-
"""Return True if the current node is parent of target_obj"""
|
375
|
-
return self == target_obj.tn_parent
|
376
|
-
|
377
|
-
def is_root(self):
|
378
|
-
"""Return True if the current node is root"""
|
379
|
-
return self.tn_parent is None
|
380
|
-
|
381
|
-
def is_root_of(self, target_obj):
|
382
|
-
"""Return True if the current node is root of target_obj"""
|
383
|
-
return self == target_obj.get_root()
|
384
|
-
|
385
|
-
def is_sibling_of(self, target_obj):
|
386
|
-
"""Return True if the current node is sibling of target_obj"""
|
387
|
-
return self in target_obj.tn_parent.tn_children.all()
|
388
|
-
|
389
|
-
# I think this method is not needed.
|
390
|
-
# Clearing entries in the Closure Table will happen automatically
|
391
|
-
# via cascading deletion
|
392
|
-
|
393
|
-
# def delete(self, cascade=True, *args, **kwargs):
|
394
|
-
# """Delete a node if cascade=True (default behaviour), children and
|
395
|
-
# descendants will be deleted too, otherwise children's parent will be
|
396
|
-
# set to None (then children become roots)"""
|
397
|
-
|
398
|
-
# pass
|
399
|
-
|
400
|
-
# Public properties
|
401
|
-
# All properties map a get_{{property}}() method.
|
402
|
-
|
403
|
-
@property
|
404
|
-
def ancestors(self):
|
405
|
-
return self.get_ancestors()
|
406
|
-
|
407
|
-
@property
|
408
|
-
def ancestors_count(self):
|
409
|
-
return self.get_ancestors_count()
|
410
|
-
|
411
|
-
@property
|
412
|
-
def ancestors_pks(self):
|
413
|
-
return self.get_ancestors_pks()
|
414
|
-
|
415
|
-
@property
|
416
|
-
def breadcrumbs(self):
|
417
|
-
return self.get_breadcrumbs()
|
418
|
-
|
419
|
-
@property
|
420
|
-
def children(self):
|
421
|
-
return self.get_children()
|
422
|
-
|
423
|
-
@property
|
424
|
-
def children_count(self):
|
425
|
-
return self.get_children_count()
|
426
|
-
|
427
|
-
@property
|
428
|
-
def children_pks(self):
|
429
|
-
return self.get_children_pks()
|
430
|
-
|
431
|
-
@property
|
432
|
-
def depth(self):
|
433
|
-
return self.get_depth()
|
434
|
-
|
435
|
-
@property
|
436
|
-
def descendants(self):
|
437
|
-
return self.get_descendants()
|
438
|
-
|
439
|
-
@property
|
440
|
-
def descendants_count(self):
|
441
|
-
return self.get_descendants_count()
|
442
|
-
|
443
|
-
@property
|
444
|
-
def descendants_pks(self):
|
445
|
-
return self.get_descendants_pks()
|
446
|
-
|
447
|
-
@property
|
448
|
-
def descendants_tree(self):
|
449
|
-
return self.get_descendants_tree()
|
450
|
-
|
451
|
-
@property
|
452
|
-
def descendants_tree_display(self):
|
453
|
-
return self.get_descendants_tree_display()
|
454
|
-
|
455
|
-
@property
|
456
|
-
def first_child(self):
|
457
|
-
return self.get_first_child()
|
458
|
-
|
459
|
-
@property
|
460
|
-
def index(self):
|
461
|
-
return self.get_index()
|
462
|
-
|
463
|
-
@property
|
464
|
-
def last_child(self):
|
465
|
-
return self.get_last_child()
|
466
|
-
|
467
|
-
@property
|
468
|
-
def level(self):
|
469
|
-
return self.get_level()
|
470
|
-
|
471
|
-
@property
|
472
|
-
def parent(self):
|
473
|
-
return self.tn_parent
|
474
|
-
|
475
|
-
@property
|
476
|
-
def parent_pk(self):
|
477
|
-
return self.get_parent_pk()
|
478
|
-
|
479
|
-
@property
|
480
|
-
def priority(self):
|
481
|
-
return self.get_priority()
|
482
|
-
|
483
|
-
@classproperty
|
484
|
-
def roots(cls):
|
485
|
-
return cls.get_roots()
|
486
|
-
|
487
|
-
@property
|
488
|
-
def root(self):
|
489
|
-
return self.get_root()
|
490
|
-
|
491
|
-
@property
|
492
|
-
def root_pk(self):
|
493
|
-
return self.get_root_pk()
|
494
|
-
|
495
|
-
@property
|
496
|
-
def siblings(self):
|
497
|
-
return self.get_siblings()
|
498
|
-
|
499
|
-
@property
|
500
|
-
def siblings_count(self):
|
501
|
-
return self.get_siblings_count()
|
502
|
-
|
503
|
-
@property
|
504
|
-
def siblings_pks(self):
|
505
|
-
return self.get_siblings_pks()
|
506
|
-
|
507
|
-
@classproperty
|
508
|
-
def tree(cls):
|
509
|
-
return cls.get_tree()
|
510
|
-
|
511
|
-
@classproperty
|
512
|
-
def tree_display(cls):
|
513
|
-
return cls.get_tree_display()
|
514
|
-
|
515
|
-
# ----------------------------------------------------------------------
|
516
|
-
# Private methods
|
517
|
-
# The usage of these methods is only allowed by developers. In future
|
518
|
-
# versions, these methods may be changed or removed without any warning.
|
519
|
-
|
520
|
-
@property
|
521
|
-
def _closure_model(self):
|
522
|
-
return self._meta.model.closure_model
|
523
|
-
|
524
|
-
@property
|
525
|
-
def tn_order(self):
|
526
|
-
path = self.get_breadcrumbs(attr='tn_priority')
|
527
|
-
return ''.join(['{:0>6g}'.format(i) for i in path])
|
528
|
-
|
529
|
-
@cached_tree_method
|
530
|
-
def object2dict(self, instance, exclude=[]):
|
531
|
-
"""Convert Class Object to python dict"""
|
532
|
-
|
533
|
-
result = dict()
|
534
|
-
|
535
|
-
if not hasattr(instance, '__dict__'):
|
536
|
-
return instance
|
537
|
-
|
538
|
-
new_subdic = dict(vars(instance))
|
539
|
-
for key, value in new_subdic.items():
|
540
|
-
if key.startswith('_') or key in exclude:
|
541
|
-
continue
|
542
|
-
result.update({key: self.object2dict(value, exclude)})
|
543
|
-
|
544
|
-
childs = instance.tn_children.all()
|
545
|
-
if childs.count() > 0:
|
546
|
-
result.update({
|
547
|
-
'children': [
|
548
|
-
obj.object2dict(obj, exclude)
|
549
|
-
for obj in childs.all()]
|
550
|
-
})
|
551
|
-
result.update({'path': instance.get_path(format_str=':d')})
|
552
|
-
return result
|
553
|
-
|
554
|
-
@cached_tree_method
|
555
|
-
def get_display(self, indent=True, mark='— '):
|
556
|
-
indentation = (mark * self.tn_ancestors_count) if indent else ''
|
557
|
-
indentation = force_str(indentation)
|
558
|
-
text = self.get_display_text()
|
559
|
-
text = force_str(text)
|
560
|
-
return indentation + text
|
561
|
-
|
562
|
-
@cached_tree_method
|
563
|
-
def get_display_text(self):
|
564
|
-
"""
|
565
|
-
Gets the text that will be indented in `get_display` method.
|
566
|
-
Returns the `treenode_display_field` value if specified,
|
567
|
-
otherwise falls back on the model's pk.
|
568
|
-
Override this method to return another field or a computed value. #27
|
569
|
-
"""
|
570
|
-
text = ''
|
571
|
-
if (hasattr(self, 'treenode_display_field') and
|
572
|
-
self.treenode_display_field is not None):
|
573
|
-
field_name = getattr(self, 'treenode_display_field')
|
574
|
-
text = getattr(self, field_name, '')
|
575
|
-
if not text and self.pk:
|
576
|
-
text = self.pk
|
577
|
-
return force_str(text)
|
578
|
-
|
579
|
-
@transaction.atomic
|
580
|
-
def _insert(self):
|
581
|
-
"""Adds a new entry to the Adjacency Table and the Closure Table"""
|
582
|
-
|
583
|
-
treenode_cache.clear()
|
584
|
-
|
585
|
-
instance = self._closure_model.objects.create(
|
586
|
-
parent=self,
|
587
|
-
child=self,
|
588
|
-
depth=0
|
589
|
-
)
|
590
|
-
instance.save()
|
591
|
-
|
592
|
-
qs = self._closure_model.objects.all()
|
593
|
-
parents = qs.filter(child=self.tn_parent).values('parent', 'depth')
|
594
|
-
children = qs.filter(parent=self).values('child', 'depth')
|
595
|
-
objects = [
|
596
|
-
self._closure_model(
|
597
|
-
parent_id=p['parent'],
|
598
|
-
child_id=c['child'],
|
599
|
-
depth=p['depth'] + c['depth'] + 1
|
600
|
-
)
|
601
|
-
for p in parents
|
602
|
-
for c in children
|
603
|
-
]
|
604
|
-
self._closure_model.objects.bulk_create(objects)
|
605
|
-
|
606
|
-
@transaction.atomic
|
607
|
-
def _move_to(self, old_parent):
|
608
|
-
treenode_cache.clear()
|
609
|
-
|
610
|
-
target = self.tn_parent
|
611
|
-
qs = self._closure_model.objects.all()
|
612
|
-
subtree = qs.filter(parent=self).values('child', 'depth')
|
613
|
-
supertree = qs.filter(child=target).values('parent', 'depth')
|
614
|
-
|
615
|
-
# Step 1. Delete
|
616
|
-
subtree_pks = [node.child.pk for node in qs.filter(parent=self)]
|
617
|
-
qs.filter(child_id__in=subtree_pks).exclude(
|
618
|
-
parent_id__in=subtree_pks).delete()
|
619
|
-
|
620
|
-
# Step 2. Insert
|
621
|
-
objects = [
|
622
|
-
self._closure_model(
|
623
|
-
parent_id=p['parent'],
|
624
|
-
child_id=c['child'],
|
625
|
-
depth=p['depth'] + c['depth'] + 1
|
626
|
-
)
|
627
|
-
for p in supertree
|
628
|
-
for c in subtree
|
629
|
-
]
|
630
|
-
self._closure_model.objects.bulk_create(objects)
|
631
|
-
|
632
|
-
def _order(self):
|
633
|
-
|
634
|
-
treenode_cache.clear()
|
635
|
-
|
636
|
-
queryset = self.get_siblings_queryset()
|
637
|
-
|
638
|
-
if self.tn_priority > queryset.count():
|
639
|
-
self.tn_priority = queryset.count()
|
640
|
-
|
641
|
-
siblings = list(node for node in queryset)
|
642
|
-
sorted_siblings = sorted(siblings, key=lambda x: x.tn_priority)
|
643
|
-
sorted_siblings.insert(self.tn_priority, self)
|
644
|
-
|
645
|
-
for index in range(len(sorted_siblings)):
|
646
|
-
sorted_siblings[index].tn_priority = index
|
647
|
-
|
648
|
-
self._meta.model.objects.bulk_update(
|
649
|
-
sorted_siblings, ('tn_priority', ))
|
650
|
-
|
651
|
-
def save(self, force_insert=False, *args, **kwargs):
|
652
|
-
treenode_cache.clear()
|
653
|
-
|
654
|
-
try:
|
655
|
-
old = self._meta.model.objects.get(pk=self.pk)
|
656
|
-
old_parent = old.tn_parent
|
657
|
-
except self._meta.model.DoesNotExist:
|
658
|
-
force_insert = True
|
659
|
-
|
660
|
-
super().save(*args, **kwargs)
|
661
|
-
self._order()
|
662
|
-
|
663
|
-
if force_insert:
|
664
|
-
self._insert()
|
665
|
-
elif old_parent != self.tn_parent:
|
666
|
-
self._move_to(old_parent)
|
667
|
-
|
668
|
-
# The end
|
@@ -1 +0,0 @@
|
|
1
|
-
|