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.
Files changed (107) hide show
  1. django_fast_treenode-3.0.0.dist-info/METADATA +203 -0
  2. django_fast_treenode-3.0.0.dist-info/RECORD +90 -0
  3. {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info}/WHEEL +1 -1
  4. treenode/admin/__init__.py +2 -7
  5. treenode/admin/admin.py +138 -209
  6. treenode/admin/changelist.py +21 -39
  7. treenode/admin/exporter.py +170 -0
  8. treenode/admin/importer.py +171 -0
  9. treenode/admin/mixin.py +291 -0
  10. treenode/apps.py +42 -20
  11. treenode/cache.py +192 -303
  12. treenode/forms.py +45 -65
  13. treenode/managers/__init__.py +4 -20
  14. treenode/managers/managers.py +216 -0
  15. treenode/managers/queries.py +233 -0
  16. treenode/managers/tasks.py +167 -0
  17. treenode/models/__init__.py +8 -5
  18. treenode/models/decorators.py +54 -0
  19. treenode/models/factory.py +44 -68
  20. treenode/models/mixins/__init__.py +2 -1
  21. treenode/models/mixins/ancestors.py +44 -20
  22. treenode/models/mixins/children.py +33 -26
  23. treenode/models/mixins/descendants.py +33 -22
  24. treenode/models/mixins/family.py +25 -15
  25. treenode/models/mixins/logical.py +23 -21
  26. treenode/models/mixins/node.py +162 -104
  27. treenode/models/mixins/properties.py +22 -16
  28. treenode/models/mixins/roots.py +59 -15
  29. treenode/models/mixins/siblings.py +46 -43
  30. treenode/models/mixins/tree.py +212 -153
  31. treenode/models/mixins/update.py +154 -0
  32. treenode/models/models.py +365 -0
  33. treenode/settings.py +28 -0
  34. treenode/static/{treenode/css → css}/tree_widget.css +1 -1
  35. treenode/static/{treenode/css → css}/treenode_admin.css +43 -2
  36. treenode/static/css/treenode_tabs.css +51 -0
  37. treenode/static/js/lz-string.min.js +1 -0
  38. treenode/static/{treenode/js → js}/tree_widget.js +9 -23
  39. treenode/static/js/treenode_admin.js +531 -0
  40. treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -0
  41. treenode/static/vendors/jquery-ui/LICENSE.txt +43 -0
  42. treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -0
  43. treenode/static/vendors/jquery-ui/images/ui-icons_444444_256x240.png +0 -0
  44. treenode/static/vendors/jquery-ui/images/ui-icons_555555_256x240.png +0 -0
  45. treenode/static/vendors/jquery-ui/images/ui-icons_777620_256x240.png +0 -0
  46. treenode/static/vendors/jquery-ui/images/ui-icons_777777_256x240.png +0 -0
  47. treenode/static/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png +0 -0
  48. treenode/static/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png +0 -0
  49. treenode/static/vendors/jquery-ui/index.html +297 -0
  50. treenode/static/vendors/jquery-ui/jquery-ui.css +438 -0
  51. treenode/static/vendors/jquery-ui/jquery-ui.js +5223 -0
  52. treenode/static/vendors/jquery-ui/jquery-ui.min.css +7 -0
  53. treenode/static/vendors/jquery-ui/jquery-ui.min.js +6 -0
  54. treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -0
  55. treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +5 -0
  56. treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -0
  57. treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +5 -0
  58. treenode/static/vendors/jquery-ui/package.json +82 -0
  59. treenode/templates/admin/treenode_changelist.html +25 -0
  60. treenode/templates/admin/treenode_import_export.html +85 -0
  61. treenode/templates/admin/treenode_rows.html +57 -0
  62. treenode/tests.py +3 -0
  63. treenode/urls.py +6 -27
  64. treenode/utils/__init__.py +0 -15
  65. treenode/utils/db/__init__.py +7 -0
  66. treenode/utils/db/compiler.py +114 -0
  67. treenode/utils/db/db_vendor.py +50 -0
  68. treenode/utils/db/service.py +84 -0
  69. treenode/utils/db/sqlcompat.py +60 -0
  70. treenode/utils/db/sqlquery.py +70 -0
  71. treenode/version.py +2 -2
  72. treenode/views/__init__.py +5 -0
  73. treenode/views/autoapi.py +91 -0
  74. treenode/views/autocomplete.py +52 -0
  75. treenode/views/children.py +41 -0
  76. treenode/views/common.py +23 -0
  77. treenode/views/crud.py +209 -0
  78. treenode/views/search.py +48 -0
  79. treenode/widgets.py +27 -44
  80. django_fast_treenode-2.1.4.dist-info/METADATA +0 -166
  81. django_fast_treenode-2.1.4.dist-info/RECORD +0 -63
  82. treenode/admin/mixins.py +0 -302
  83. treenode/managers/adjacency.py +0 -205
  84. treenode/managers/closure.py +0 -278
  85. treenode/models/adjacency.py +0 -342
  86. treenode/models/classproperty.py +0 -27
  87. treenode/models/closure.py +0 -122
  88. treenode/static/treenode/js/.gitkeep +0 -1
  89. treenode/static/treenode/js/treenode_admin.js +0 -131
  90. treenode/templates/admin/export_success.html +0 -26
  91. treenode/templates/admin/tree_node_changelist.html +0 -19
  92. treenode/templates/admin/tree_node_export.html +0 -27
  93. treenode/templates/admin/tree_node_import.html +0 -45
  94. treenode/templates/admin/tree_node_import_report.html +0 -32
  95. treenode/templates/widgets/tree_widget.css +0 -23
  96. treenode/utils/aid.py +0 -46
  97. treenode/utils/base16.py +0 -38
  98. treenode/utils/base36.py +0 -37
  99. treenode/utils/db.py +0 -116
  100. treenode/utils/exporter.py +0 -196
  101. treenode/utils/importer.py +0 -328
  102. treenode/utils/radix.py +0 -61
  103. treenode/views.py +0 -184
  104. {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info/licenses}/LICENSE +0 -0
  105. {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info}/top_level.txt +0 -0
  106. /treenode/static/{treenode → css}/.gitkeep +0 -0
  107. /treenode/static/{treenode/css → js}/.gitkeep +0 -0
@@ -2,13 +2,23 @@
2
2
  """
3
3
  TreeNode Roots Mixin
4
4
 
5
- Version: 2.1.0
5
+ Version: 3.0.0
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
9
9
 
10
10
  from django.db import models
11
- from treenode.cache import cached_method
11
+ from ...cache import cached_method
12
+
13
+
14
+ '''
15
+ try:
16
+ profile
17
+ except NameError:
18
+ def profile(func):
19
+ """Profile."""
20
+ return func
21
+ '''
12
22
 
13
23
 
14
24
  class TreeNodeRootsMixin(models.Model):
@@ -51,46 +61,80 @@ class TreeNodeRootsMixin(models.Model):
51
61
  instance = cls(**kwargs)
52
62
 
53
63
  parent, priority = cls._get_place(None, position)
54
- instance.tn_parent = None
55
- instance.tn_priority = priority
64
+ instance.parent = None
65
+ instance.priority = priority
56
66
  instance.save()
57
67
  return instance
58
68
 
59
69
  @classmethod
60
- @cached_method
61
70
  def get_roots_queryset(cls):
62
71
  """Get root nodes queryset with preloaded children."""
63
- qs = cls.objects.filter(tn_parent=None).prefetch_related('tn_children')
64
- return qs
72
+ return cls.objects.filter(parent_id__isnull=True)
65
73
 
66
74
  @classmethod
67
- @cached_method
68
75
  def get_roots_pks(cls):
69
76
  """Get a list with all root nodes."""
70
- pks = cls.objects.filter(tn_parent=None).values_list("id", flat=True)
71
- return list(pks)
77
+ queryset = cls.objects.filter(parent_id__isnull=True)
78
+ return queryset.values_list("id", flat=True)
72
79
 
73
80
  @classmethod
81
+ @cached_method
74
82
  def get_roots(cls):
75
83
  """Get a list with all root nodes."""
76
- qs = cls.get_roots_queryset()
77
- return list(qs)
84
+ return list(cls.objects.filter(parent__isnull=True))
78
85
 
79
86
  @classmethod
80
87
  def get_roots_count(cls):
81
88
  """Get a list with all root nodes."""
82
- return len(cls.get_roots_pks())
89
+ return cls.objects.filter(parent__isnull=True).count()
83
90
 
84
91
  @classmethod
85
92
  def get_first_root(cls):
86
93
  """Return the first root node in the tree or None if it is empty."""
87
94
  roots = cls.get_roots_queryset()
88
- return roots.fiest() if roots.count() > 0 else None
95
+ return roots.first()
89
96
 
90
97
  @classmethod
91
98
  def get_last_root(cls):
92
99
  """Return the last root node in the tree or None if it is empty."""
93
100
  roots = cls.get_roots_queryset()
94
- return roots.last() if roots.count() > 0 else None
101
+ return roots.last()
102
+
103
+ @classmethod
104
+ def sort_roots(cls):
105
+ """
106
+ Re-sort root nodes.
107
+
108
+ Sorts all nodes with parent_id IS NULL using a raw SQL query with
109
+ a window function.
110
+ The new ordering is computed based on the model's sorting_field
111
+ (defaulting to 'priority').
112
+ It updates the 'priority' field for all root nodes.
113
+ """
114
+ from django.db import connection
115
+
116
+ db_table = cls._meta.db_table
117
+ ordering_field = cls.sorting_field
118
+
119
+ # Only root nodes have parent_id IS NULL
120
+ where_clause = "parent_id IS NULL"
121
+ params = []
122
+
123
+ query = f"""
124
+ WITH ranked AS (
125
+ SELECT id,
126
+ ROW_NUMBER() OVER (ORDER BY {ordering_field}) - 1 AS new_priority
127
+ FROM {db_table}
128
+ WHERE {where_clause}
129
+ )
130
+ UPDATE {db_table} AS t
131
+ SET priority = ranked.new_priority
132
+ FROM ranked
133
+ WHERE t.id = ranked.id;
134
+ """
135
+
136
+ with connection.cursor() as cursor:
137
+ cursor.execute(query, params)
138
+
95
139
 
96
140
  # The End
@@ -2,13 +2,23 @@
2
2
  """
3
3
  TreeNode Siblings Mixin
4
4
 
5
- Version: 2.1.0
5
+ Version: 3.0.0
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
9
9
 
10
10
  from django.db import models
11
- from treenode.cache import cached_method
11
+ from ...cache import cached_method
12
+
13
+
14
+ '''
15
+ try:
16
+ profile
17
+ except NameError:
18
+ def profile(func):
19
+ """Profile."""
20
+ return func
21
+ '''
12
22
 
13
23
 
14
24
  class TreeNodeSiblingsMixin(models.Model):
@@ -26,67 +36,59 @@ class TreeNodeSiblingsMixin(models.Model):
26
36
  Returns the created node object or None if failed. It will be saved
27
37
  by this method.
28
38
  """
29
- if isinstance(position, int):
30
- priority = position
31
- parent = self.tn_parent
32
- else:
33
- if position not in [
34
- 'first-sibling', 'left-sibling', 'right-sibling',
35
- 'last-sibling', 'sorted-sibling']:
36
- raise ValueError(f"Invalid position format: {position}")
37
- parent, priority = self._meta.model._get_place(self, position)
38
-
39
39
  instance = kwargs.get("instance")
40
40
  if instance is None:
41
41
  instance = self._meta.model(**kwargs)
42
- instance.tn_priority = priority
43
- instance.tn_priority = parent
42
+
43
+ parent, priority = self._meta.model._get_place(self.patent, position)
44
+
45
+ instance.parent = parent
46
+ instance.priority = priority
44
47
  instance.save()
48
+ if isinstance(position, str) and 'sorted' in position:
49
+ self._sort_siblings()
45
50
  return instance
46
51
 
47
- @cached_method
48
52
  def get_siblings_queryset(self, include_self=True):
49
- """Get the siblings queryset with prefetch."""
50
- if self.tn_parent:
51
- qs = self._meta.model.objects.filter(tn_parent=self.tn_parent)
52
- else:
53
- qs = self._meta.model.objects.filter(tn_parent__isnull=True)
54
- return qs if include_self else qs.exclude(pk=self.pk)
53
+ """Get Siblings QuerySet."""
54
+ qs = self._meta.model.objects.filter(parent_id=self._parent_id)
55
+ return qs if include_self else qs.exclude(pk=self.id)
55
56
 
57
+ # @profile
58
+ @cached_method
56
59
  def get_siblings(self, include_self=True):
57
60
  """Get a list with all the siblings."""
58
- return list(self.get_siblings_queryset())
59
-
60
- def get_siblings_count(self, include_self=True):
61
- """Get the siblings count."""
62
- return self.get_siblings_queryset(include_self).count()
61
+ queryset = self._meta.model.objects.filter(parent=self.parent)
62
+ queryset = queryset if include_self else queryset.exclude(pk=self.pk)
63
+ return [n for n in queryset]
63
64
 
65
+ @cached_method
64
66
  def get_siblings_pks(self, include_self=True):
65
67
  """Get the siblings pks list."""
66
- return [item.pk for item in self.get_siblings_queryset(include_self)]
68
+ return self.query("siblings", include_self)
67
69
 
68
- def get_first_sibling(self):
69
- """
70
- Return the fist node’s sibling.
70
+ @cached_method
71
+ def get_siblings_count(self):
72
+ """Get the siblings count."""
73
+ qs = self.query("siblings")
74
+ return qs.count()
71
75
 
72
- Method can return the node itself if it was the leftmost sibling.
73
- """
74
- return self.get_siblings_queryset().fist()
76
+ @cached_method
77
+ def get_first_sibling(self):
78
+ """Return the first sibling in the tree, or None."""
79
+ qs = self._meta.model.objects.filter(parent_id=self._parent_id)
80
+ return qs.first()
75
81
 
82
+ @cached_method
76
83
  def get_previous_sibling(self):
77
84
  """Return the previous sibling in the tree, or None."""
78
- priority = self.tn_priority - 1
79
- if priority < 0:
80
- return None
81
- return self.get_siblings_queryset.filter(tn_priority=priority)
85
+ options = {'parent_id': self._parent_id, 'priority__lt': self.priority}
86
+ return self._meta.model.objects.filter(**options).last()
82
87
 
83
88
  def get_next_sibling(self):
84
89
  """Return the next sibling in the tree, or None."""
85
- priority = self.tn_priority = 1
86
- queryset = self.get_siblings_queryset()
87
- if priority == queryset.count():
88
- return None
89
- return queryset.filter(tn_priority=priority)
90
+ options = {'parent_id': self._parent_id, 'priority__gt': self.priority}
91
+ return self._meta.model.objects.filter(**options).first()
90
92
 
91
93
  def get_last_sibling(self):
92
94
  """
@@ -94,6 +96,7 @@ class TreeNodeSiblingsMixin(models.Model):
94
96
 
95
97
  Method can return the node itself if it was the leftmost sibling.
96
98
  """
97
- return self.get_siblings_queryset().last()
99
+ qs = self._meta.model.objects.filter(parent_id=self._parent_id)
100
+ return qs.last()
98
101
 
99
102
  # The End