django-fast-treenode 3.0.2__py3-none-any.whl → 3.0.4__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 (25) hide show
  1. {django_fast_treenode-3.0.2.dist-info → django_fast_treenode-3.0.4.dist-info}/METADATA +24 -25
  2. {django_fast_treenode-3.0.2.dist-info → django_fast_treenode-3.0.4.dist-info}/RECORD +25 -25
  3. {django_fast_treenode-3.0.2.dist-info → django_fast_treenode-3.0.4.dist-info}/WHEEL +1 -1
  4. treenode/admin/admin.py +1 -0
  5. treenode/admin/mixin.py +1 -1
  6. treenode/managers/queries.py +6 -18
  7. treenode/managers/tasks.py +80 -42
  8. treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -384
  9. treenode/static/vendors/jquery-ui/LICENSE.txt +43 -43
  10. treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -10716
  11. treenode/static/vendors/jquery-ui/index.html +297 -297
  12. treenode/static/vendors/jquery-ui/jquery-ui.css +438 -438
  13. treenode/static/vendors/jquery-ui/jquery-ui.js +5222 -5222
  14. treenode/static/vendors/jquery-ui/jquery-ui.min.css +6 -6
  15. treenode/static/vendors/jquery-ui/jquery-ui.min.js +5 -5
  16. treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -16
  17. treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +4 -4
  18. treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -439
  19. treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +4 -4
  20. treenode/static/vendors/jquery-ui/package.json +82 -82
  21. treenode/utils/db/sqlcompat.py +33 -1
  22. treenode/utils/db/sqlquery.py +24 -0
  23. treenode/version.py +2 -2
  24. {django_fast_treenode-3.0.2.dist-info → django_fast_treenode-3.0.4.dist-info}/licenses/LICENSE +0 -0
  25. {django_fast_treenode-3.0.2.dist-info → django_fast_treenode-3.0.4.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-fast-treenode
3
- Version: 3.0.2
3
+ Version: 3.0.4
4
4
  Summary: Treenode Framework for supporting tree (hierarchical) data structure in Django projects
5
5
  Home-page: https://django-fast-treenode.readthedocs.io/
6
6
  Author: Timur Kady
@@ -53,7 +53,7 @@ Classifier: Operating System :: OS Independent
53
53
  Requires-Python: >=3.9
54
54
  Description-Content-Type: text/markdown
55
55
  License-File: LICENSE
56
- Requires-Dist: Django>=4.0
56
+ Requires-Dist: Django>=5.0
57
57
  Requires-Dist: msgpack>=1.0.0
58
58
  Requires-Dist: openpyxl>=3.0.0
59
59
  Requires-Dist: pyyaml>=5.1
@@ -62,16 +62,16 @@ Dynamic: home-page
62
62
  Dynamic: license-file
63
63
  Dynamic: requires-python
64
64
 
65
- # TreeNode Framework
65
+ # Treenode Framework
66
66
  **A hybrid open-source framework for working with trees in Django**
67
67
 
68
68
  [![Tests](https://github.com/TimurKady/django-fast-treenode/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/TimurKady/django-fast-treenode/actions/workflows/test.yaml)
69
69
  [![Docs](https://readthedocs.org/projects/django-fast-treenode/badge/?version=latest)](https://django-fast-treenode.readthedocs.io/)
70
- [![PyPI](https://img.shields.io/pypi/v/django-fast-treenode.svg)](https://pypi.org/project/django-fast-treenode/)
70
+ [![PyPI](https://img.shields.io/pypi/v/django-fast-treenode.svg)](https://pypi.org/project/django-fast-Treenode/)
71
71
  [![Published on Django Packages](https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26)](https://djangopackages.org/packages/p/django-fast-treenode/)
72
72
  [![Sponsor](https://img.shields.io/github/sponsors/TimurKady)](https://github.com/sponsors/TimurKady)
73
73
 
74
- ## About The TreeNode Framework
74
+ ## About The Treenode Framework
75
75
  ### Overview
76
76
 
77
77
  **Treenode Framework** is an advanced tree management system for Django applications.It is designed to handle large-scale, deeply nested, and highly dynamic tree structures while maintaining excellent performance, data integrity, and ease of use.
@@ -87,7 +87,7 @@ Its core philosophy: **maximum scalability, minimum complexity**.
87
87
 
88
88
  ### Key Features
89
89
  #### Common operations
90
- The `django-fast-treenode` package supports all the basic operations needed to work with tree structures:
90
+ The `django-fast-Treenode` package supports all the basic operations needed to work with tree structures:
91
91
 
92
92
  - Extracting **ancestors** (queryset, list, pks, count);
93
93
  - Extracting **children** (queryset, list, pks, count);
@@ -118,10 +118,10 @@ Typical applications include:
118
118
 
119
119
  In all these domains, scalable and fast tree management is not a luxury — it's a necessity.
120
120
 
121
- ### Why TreeNode Framework?
121
+ ### Why Treenode Framework?
122
122
  At the moment, django-fast-treeenode is, if not the best, then one of the best packages for working with tree data under Djangjo.
123
123
 
124
- - **High performance**: [tests show](docs/about.md#benchmark-tests) that on trees of 5k-10k nodes with a nesting depth of 500-600 levels, **Treenode Framework** (`django-fast-treenode`) shows **performance 4-7 times better** than the main popular packages.
124
+ - **High performance**: [tests show](docs/about.md#benchmark-tests) that on trees of 5k-10k nodes with a nesting depth of 500-600 levels, **Treenode Framework** (`django-fast-Treenode`) shows **performance 4-7 times better** than the main popular packages.
125
125
  - **Flexible API**: today contains the widest set of methods for working with a tree in comparison with other packages.
126
126
  - **Convenient administration**: the admin panel interface was developed taking into account the experience of using other packages. It provides convenience and intuitiveness with ease of programming.
127
127
  - **Scalability**: **Treenode Framework** suitable for solving simple problems such as menus, directories, parsing arithmetic expressions, as well as complex problems such as program optimization, image layout, multi-step decision making problems, or machine learning..
@@ -134,7 +134,7 @@ To get started quickly, you need to follow these steps:
134
134
 
135
135
  - Simply install the package via `pip`:
136
136
  ```sh
137
- pip install django-fast-treenode
137
+ pip install django-fast-Treenode
138
138
  ```
139
139
  - Once installed, add `'treenode'` to your `INSTALLED_APPS` in **settings.py**:
140
140
  ```python {title="settings.py"}
@@ -147,9 +147,9 @@ To get started quickly, you need to follow these steps:
147
147
 
148
148
  - Open **models.py** and create your own tree class:
149
149
  ```
150
- from treenode.models import TreeNodeModel
150
+ from Treenode.models import TreenodeModel
151
151
 
152
- class MyTree(TreeNodeModel):
152
+ class MyTree(TreenodeModel):
153
153
  name = models.CharField(max_length=255)
154
154
  display_field = "name"
155
155
  ```
@@ -157,11 +157,11 @@ To get started quickly, you need to follow these steps:
157
157
  - Open **admin.py** and create a model for the admin panel
158
158
  ```
159
159
  from django.contrib import admin
160
- from treenode.admin import TreeNodeModelAdmin
161
- from .models import TestNode
160
+ from Treenode.admin import TreenodeModelAdmin
161
+ from .models import MyTree
162
162
 
163
- @admin.register(TestNode)
164
- class EntityAdmin(TreeNodeModelAdmin):
163
+ @admin.register(MyTree)
164
+ class MyTreeAdmin(TreenodeModelAdmin):
165
165
  list_display = ("name",)
166
166
  search_fields = ("name",)
167
167
  ```
@@ -176,25 +176,24 @@ To get started quickly, you need to follow these steps:
176
176
  ```sh
177
177
  python manage.py runserver
178
178
  ```
179
-
180
179
  Everything is ready, enjoy 🎉!
181
180
 
182
181
  ## Documentation
183
- Full documentation is available at **[ReadTheDocs](https://django-fast-treenode.readthedocs.io/)**.
182
+ Full documentation is available at **[ReadTheDocs](https://django-fast-Treenode.readthedocs.io/)**.
184
183
 
185
184
  Quick access links:
186
- * [Installation, configuration and fine tuning](https://django-fast-treenode.readthedocs.io/installation/)
187
- * [Model Inheritance and Extensions](https://django-fast-treenode.readthedocs.io/models/)
188
- * [Working with Admin Classes](https://django-fast-treenode.readthedocs.io/admin/)
189
- * [API Reference](https://django-fast-treenode.readthedocs.io/api/)
190
- * [Import & Export](https://django-fast-treenode.readthedocs.io/import_export/)
191
- * [Caching and working with cache](https://django-fast-treenode.readthedocs.io/cache/)
192
- * [Migration and upgrade guide](https://django-fast-treenode.readthedocs.io/migration/)
185
+ * [Installation, configuration and fine tuning](https://django-fast-Treenode.readthedocs.io/installation/)
186
+ * [Model Inheritance and Extensions](https://django-fast-Treenode.readthedocs.io/models/)
187
+ * [Working with Admin Classes](https://django-fast-Treenode.readthedocs.io/admin/)
188
+ * [API Reference](https://django-fast-Treenode.readthedocs.io/api/)
189
+ * [Import & Export](https://django-fast-Treenode.readthedocs.io/import_export/)
190
+ * [Caching and working with cache](https://django-fast-Treenode.readthedocs.io/cache/)
191
+ * [Migration and upgrade guide](https://django-fast-Treenode.readthedocs.io/migration/)
193
192
 
194
193
  Your wishes, objections, comments are welcome.
195
194
 
196
195
  ## License
197
- Released under [MIT License](https://github.com/TimurKady/django-fast-treenode/blob/main/LICENSE).
196
+ Released under [MIT License](https://github.com/TimurKady/django-fast-Treenode/blob/main/LICENSE).
198
197
 
199
198
  ## Credits
200
199
  Thanks to everyone who contributed to the development and testing of this package, as well as the Django community for their inspiration and support.
@@ -1,4 +1,4 @@
1
- django_fast_treenode-3.0.2.dist-info/licenses/LICENSE,sha256=SSYqS84FCnAW7tAxmjBKU8qAa8Jv4VGPuSSGeHwWtJE,1095
1
+ django_fast_treenode-3.0.4.dist-info/licenses/LICENSE,sha256=SSYqS84FCnAW7tAxmjBKU8qAa8Jv4VGPuSSGeHwWtJE,1095
2
2
  treenode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  treenode/apps.py,sha256=QlwjNDM9rkUoWB8Vm8-OkS6lNx0-aTByuGZlu9wrQMs,1832
4
4
  treenode/cache.py,sha256=2jUiiecfFxwB7QFukpU4u0FnDzGH6hNRfo6KAYvs6vM,8447
@@ -7,18 +7,18 @@ treenode/settings.py,sha256=oSkcKXNVd28HXrlWZIH2VYinCMq-UdCDlX4KD0Qc_Xk,631
7
7
  treenode/signals.py,sha256=ERrlKjGqhYaPYVKKRk1JBBlPFOmJKpJ6bXsJavcTlo0,518
8
8
  treenode/tests.py,sha256=2uDafv3Ns6f7Vy1ekUtgYxCZEi1KRyesZDTAFhYcX-E,63
9
9
  treenode/urls.py,sha256=krHvVigc_dxC0z5hEd2rgeH6th8jW7qJY3Qbia-419Y,240
10
- treenode/version.py,sha256=TN9nZNpy8llm9fqIJAWYPQd8TagW5fxnW0I69gWD-oY,220
10
+ treenode/version.py,sha256=agtMVvI22kmKiOiAyXlZeP14ej675T1Yd8F0FzHfXXk,220
11
11
  treenode/widgets.py,sha256=61ed16bVqb1_R97jekDrbKS5pDVK4nzXSDwL3CDBYEk,4075
12
12
  treenode/admin/__init__.py,sha256=XNEYHdF5lKb0vpdlVxdR2fxj5oUgzyx1YyCwsv0gxHw,100
13
- treenode/admin/admin.py,sha256=xhf6tT5Ydn6i_upUDA1A6TN_muNndCko8gzM_fNIex4,7707
13
+ treenode/admin/admin.py,sha256=9_GC6JjWRiG_G84royRGAr10BSNIAj4N_33JXdzP8IY,7733
14
14
  treenode/admin/changelist.py,sha256=KUYS9MaR8Ck_1xmMqupobxWKarrJEqmHuEG32CL01Bo,1662
15
15
  treenode/admin/exporter.py,sha256=QE74V6W3tvwA5kCvBt1MmVlLOaWh-o8EU63cgmiwD5Q,5724
16
16
  treenode/admin/importer.py,sha256=hK3D-1DZcoowGblRluGzng3n5Bf__hMsbNaIGXRpRdg,6263
17
- treenode/admin/mixin.py,sha256=yXcSpBEfoMYT7tuAbHhGbuqlVQkCR5RizW2bNWJ0QNM,10639
17
+ treenode/admin/mixin.py,sha256=o83zTETOjvdHRjGR2pGthsY_RO7GVEimNUISgqqOBLs,10642
18
18
  treenode/managers/__init__.py,sha256=c7F9Ku9489Hv6lTpUY2nbyBlWFCXBWAkNBm4xTKcjL8,186
19
19
  treenode/managers/managers.py,sha256=8OaFxtajyR1d7-UHyiUbifMBEF9cjfHTIEYPkYUWmt0,7166
20
- treenode/managers/queries.py,sha256=Kepax8SDn7G5tOlPRWBCp5Oyp49O5iMITCMBoNCm_Ak,10655
21
- treenode/managers/tasks.py,sha256=gqZfqeOrdPtTJZ1q83aCFh6htfxP0XMTOkd1KWL9PVU,5227
20
+ treenode/managers/queries.py,sha256=zZYeDVFXl-Hro9ubv0_zuN7XaVXQhyeeFx_GVZSMi30,10273
21
+ treenode/managers/tasks.py,sha256=b8deUAbCpD1Yov-PpjKNAx49rL4fejkGf64ih5JziF0,7138
22
22
  treenode/models/__init__.py,sha256=iR4ksCKoayvkIWWgGk6OUGHZC3D0mzAtgdBcS2vQPBw,188
23
23
  treenode/models/decorators.py,sha256=N2dcnWqSCiEXDcYCf0zVijrbGUC8kYlqOLi_GKFmECU,1457
24
24
  treenode/models/factory.py,sha256=sPUSrvo1za-r6ny3B8ptwevyjO8-iUpPNrT0eSD2kvI,1786
@@ -44,19 +44,19 @@ treenode/static/js/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
44
44
  treenode/static/js/lz-string.min.js,sha256=TAnTJQd2AlLqT9M2TU7GFjnoj9SIfwLeZnpEtLkP624,4718
45
45
  treenode/static/js/tree_widget.js,sha256=2LU8IIJD5TXbWVGjCEgrj5aYnUYtwBZECzj25XLtxNc,9633
46
46
  treenode/static/js/treenode_admin.js,sha256=fkxAj50QxwpPVdQxDNxQZ5vR0goUt53h8FzOv_fjxBQ,15945
47
- treenode/static/vendors/jquery-ui/AUTHORS.txt,sha256=geZ7JfVsTJq8Wgjrcm1p56Zqjxx7PL9FkxhKiN8dQg8,14848
48
- treenode/static/vendors/jquery-ui/LICENSE.txt,sha256=9kkCXPZcjc2FiLCTeIO6elK9ttNRKUZKNqFgLrACUPE,1818
49
- treenode/static/vendors/jquery-ui/index.html,sha256=RSv8cA662qJdc02OX-UmpaBJoZrngoQqnNWVZNn9GQQ,24229
50
- treenode/static/vendors/jquery-ui/jquery-ui.css,sha256=O8mmheetXPeKUa-iDk8fdxDhs096eWIi10qLJb6OXyw,17171
51
- treenode/static/vendors/jquery-ui/jquery-ui.js,sha256=XtnlPiKuVzMh-cef76mBCNCTPqk2cIlKBgmTHfRBDog,146430
52
- treenode/static/vendors/jquery-ui/jquery-ui.min.css,sha256=Yv2MureLOGZicbgEv28X9ft9o0SRBZ84PWIgg6h_V2E,15003
53
- treenode/static/vendors/jquery-ui/jquery-ui.min.js,sha256=FT6F7DDm5J1Qz6B5fv3e8jnmx9QyyfR2qUeTptb73bA,70364
54
- treenode/static/vendors/jquery-ui/jquery-ui.structure.css,sha256=392jP1trXFqS1OVv2X50px0F8w1DeTyIvvHL9wxNXi0,328
55
- treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css,sha256=cXLe7UDVCN5SvPpbb3HJdndQIk1hlWMrzyRRYO9rcLs,208
56
- treenode/static/vendors/jquery-ui/jquery-ui.theme.css,sha256=grsB4hVvwiBxnNsG1c_ocH1YK76CExOav_dn_BsxepU,17140
57
- treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css,sha256=oW6e3DmGiKSNLzo3HCUnjes9ykGZ6gXEtl3LjiQIkMs,13708
58
- treenode/static/vendors/jquery-ui/package.json,sha256=AvCATruWzbwY7NsfzcUR2WqPjslGO8M7hHzTj1AYDQY,2122
59
- treenode/static/vendors/jquery-ui/external/jquery/jquery.js,sha256=eKhayi8LEQwp4NKxN-CfCh-3qOVUtJn3QNZ0TciWLP4,285314
47
+ treenode/static/vendors/jquery-ui/AUTHORS.txt,sha256=7eSHeezBdJ7Qlq0sbIvY4VRvqpMwLCAsm5ur1GqFBV8,15232
48
+ treenode/static/vendors/jquery-ui/LICENSE.txt,sha256=apBrQ--OzcBj7ylvGUYGVfV3jIaoB2ZPm63a1EU0Hko,1861
49
+ treenode/static/vendors/jquery-ui/index.html,sha256=fFm161yOy1n-_mJRFXB5oOYki_8Dlv-4ym3UZlrBiFw,24526
50
+ treenode/static/vendors/jquery-ui/jquery-ui.css,sha256=Q5bjan1qvGRnmfsMY79xzgnzXgNfHpKITyFwDwYDXTE,17609
51
+ treenode/static/vendors/jquery-ui/jquery-ui.js,sha256=wo5jyZcNCjgTOWkHAG5I-AHnELJBiMT7PB86Hkn981w,151652
52
+ treenode/static/vendors/jquery-ui/jquery-ui.min.css,sha256=FS9BPkDJmv5x5xNd4OpSxisTNOEUUC3cry5lZo9TXNU,15009
53
+ treenode/static/vendors/jquery-ui/jquery-ui.min.js,sha256=ymdFCtss_PVmZcIYetqzjnF_jiQur3VPdv4bjqIPA74,70369
54
+ treenode/static/vendors/jquery-ui/jquery-ui.structure.css,sha256=Sc38kpjVG4BhkslXC-wnctVgd7RO4NAVnNinveoLumU,344
55
+ treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css,sha256=3Hh94kZQ7IgAV86zPR-eYJheU0GvPFXXa0ZHldtjim4,212
56
+ treenode/static/vendors/jquery-ui/jquery-ui.theme.css,sha256=0cqVKFAVS9NOQyqhyhpanAO_AQnoPsX715VK0iIMGDE,17579
57
+ treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css,sha256=TdVk-kqTdbRzjVI7AfPMWaIJG2pOIlnZk4hUAW3bl3E,13712
58
+ treenode/static/vendors/jquery-ui/package.json,sha256=bDQqA98dfsEi83upFeZ8RipVtAraPRoxZMxcv244oQo,2204
59
+ treenode/static/vendors/jquery-ui/external/jquery/jquery.js,sha256=6440qEDaqjKqrIVfk4x21neDBVsbef6XUR5dUCKBv_E,296030
60
60
  treenode/static/vendors/jquery-ui/images/ui-icons_444444_256x240.png,sha256=YW09ms2dyfoVEOT_NEpaWXMMRLJxVIqu8hku63SoCEA,7107
61
61
  treenode/static/vendors/jquery-ui/images/ui-icons_555555_256x240.png,sha256=HP6KoU5aOllbVtaudhmNOLhBIzi_0oCuxBjwyhDATOo,7105
62
62
  treenode/static/vendors/jquery-ui/images/ui-icons_777620_256x240.png,sha256=NxKGIMNmb6YYZor0RuijQ7PTK5BoA9b5qXLjYTMFvXo,4615
@@ -75,8 +75,8 @@ treenode/utils/db/__init__.py,sha256=RwicAcJSI1nhIPWLdT7j9TFsgOc9834VDn9lVn54GlY
75
75
  treenode/utils/db/compiler.py,sha256=PgD9ybS5H8OUHw1gkFBQHhnrf5HiCx8QXUMRhydwh7o,3824
76
76
  treenode/utils/db/db_vendor.py,sha256=4SyEHl51jVCDB3is4omHCf2bTB_QV3RvemUQYxJP5m0,930
77
77
  treenode/utils/db/service.py,sha256=PF85Yhz2xUWFFCzpLYotmiNTZXXEH61rhswslSxEUds,2640
78
- treenode/utils/db/sqlcompat.py,sha256=K71ggkKIvpdTtHQ6Y4qcbo6cj2eYiEfy6DlVBr8Po1E,4460
79
- treenode/utils/db/sqlquery.py,sha256=KXcfKbbaBF-D134H_2DiPQtjedR79SJNXPJc0msZYEc,1938
78
+ treenode/utils/db/sqlcompat.py,sha256=FTgEMu3kQ1c96nB1sUW-SFOmYEvdl8JlD9HvsRj_dO0,5704
79
+ treenode/utils/db/sqlquery.py,sha256=jHI0gLkdJCD6ulOenO4b0aSjyAxtJ4ZGvsQa0i3Aj8E,2958
80
80
  treenode/views/__init__.py,sha256=ppxbBx51TUaKstJFpAd_DTmbKjbZGmVMLNYSpgUKnd0,111
81
81
  treenode/views/autoapi.py,sha256=o75e8IFsogbhZN_rbx3BKVnoruD96nWelnC5UzOqUDw,3628
82
82
  treenode/views/autocomplete.py,sha256=Z7cBnC4Ihdyxm8zlbnG6CkZdVkM3TOTWRpw5mdhaIVA,1469
@@ -84,7 +84,7 @@ treenode/views/children.py,sha256=bygXaEBExxG3zIPL34_PYHLFFIqlQU2naqPIlyQ6e-s,11
84
84
  treenode/views/common.py,sha256=mrmr40R91XVbMWcz5GZT-OjpnQ87F7XQZxu1W6rqpqI,617
85
85
  treenode/views/crud.py,sha256=RI5rdyD4hZTszjZFThByxi_lkAeJlqbDCXFkD8iyzKE,7424
86
86
  treenode/views/search.py,sha256=c_GyooT3jyoNa96bBxfoWruRN1wIw-ZGYvwGKkGojTs,1501
87
- django_fast_treenode-3.0.2.dist-info/METADATA,sha256=Q5sQAXL8xAh-5T9r36G5IsxzvFiQJhK0UxG8xm-xC4o,10255
88
- django_fast_treenode-3.0.2.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
89
- django_fast_treenode-3.0.2.dist-info/top_level.txt,sha256=fmgxHbXyx1O2MPi_9kjx8aL9L-8TmV0gre4Go8XgqFk,9
90
- django_fast_treenode-3.0.2.dist-info/RECORD,,
87
+ django_fast_treenode-3.0.4.dist-info/METADATA,sha256=NbOy_OHnyPjubLhbssbpdM2jwGiModfoiE_CPNvQpM8,10249
88
+ django_fast_treenode-3.0.4.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
89
+ django_fast_treenode-3.0.4.dist-info/top_level.txt,sha256=fmgxHbXyx1O2MPi_9kjx8aL9L-8TmV0gre4Go8XgqFk,9
90
+ django_fast_treenode-3.0.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.1.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
treenode/admin/admin.py CHANGED
@@ -47,6 +47,7 @@ class TreeNodeModelAdmin(AdminMixin, admin.ModelAdmin):
47
47
  }
48
48
 
49
49
  change_list_template = "admin/treenode_changelist.html"
50
+ import_export = True
50
51
 
51
52
  class Media:
52
53
  """Meta Class."""
treenode/admin/mixin.py CHANGED
@@ -84,7 +84,7 @@ class AdminMixin(admin.ModelAdmin):
84
84
  value = field(obj)
85
85
  field_name = getattr(field, "__name__", "field")
86
86
  else:
87
- attr, value = lookup_field(field, obj, self)
87
+ r, attr, value = lookup_field(field, obj, self)
88
88
  field_name = field
89
89
 
90
90
  row_data.append(value)
@@ -5,13 +5,14 @@ Low-level SQL Query Manager.
5
5
  Encapsulates all logic to retrieve related primary keys based on relationships
6
6
  (e.g., ancestors, children, descendants, siblings, family, root) using raw SQL.
7
7
 
8
- Version: 3.0.0
8
+ Version: 3.0.4
9
9
  Author: Timur Kady
10
10
  Email: timurkady@yandex.com
11
11
  """
12
12
 
13
13
 
14
14
  from django.db import connection
15
+ from ..utils.db.sqlcompat import SQLCompat
15
16
 
16
17
 
17
18
  class TreeQuery:
@@ -32,19 +33,6 @@ class TreeQuery:
32
33
  cursor.execute(sql, params)
33
34
  return cursor.fetchall()
34
35
 
35
- def wrap_union_all(self, queries):
36
- """
37
- Combine multiple SQL queries using UNION ALL.
38
-
39
- Each query is a tuple: (sql, params).
40
- Returns a tuple: (combined_sql, combined_params).
41
- """
42
- union_query = " UNION ALL ".join(f"({q[0]})" for q in queries)
43
- combined_params = []
44
- for q in queries:
45
- combined_params.extend(q[1])
46
- return union_query, combined_params
47
-
48
36
  def order_by(self, sql, order_by_clause):
49
37
  """Wrap the SQL in an outer query to enforce ordering."""
50
38
  return f"SELECT * FROM ({sql}) AS combined ORDER BY {order_by_clause}"
@@ -76,7 +64,7 @@ class TreeQuery:
76
64
  if include_self:
77
65
  sql2 = f"SELECT id, priority FROM {self.db_table} WHERE id = %s"
78
66
  params2 = [self.node.pk]
79
- combined_sql, combined_params = self.wrap_union_all(
67
+ combined_sql, combined_params = SQLCompat.wrap_union_all(
80
68
  [(sql1, params1), (sql2, params2)])
81
69
  sql = self.order_by(combined_sql, "priority")
82
70
  return sql, combined_params
@@ -115,7 +103,7 @@ class TreeQuery:
115
103
  FROM {self.db_table}
116
104
  WHERE id = %s
117
105
  """
118
- union_sql, union_params = self.wrap_union_all([
106
+ union_sql, union_params = SQLCompat.wrap_union_all([
119
107
  (base_sql, params),
120
108
  (sql_self, [self.node.pk])
121
109
  ])
@@ -148,7 +136,7 @@ class TreeQuery:
148
136
 
149
137
  if include_self:
150
138
  sql_self = f"SELECT id, _depth, priority FROM {self.db_table} WHERE id = %s" # noqa: D501
151
- union_sql, union_params = self.wrap_union_all(
139
+ union_sql, union_params = SQLCompat.wrap_union_all(
152
140
  [(base_sql, params), (sql_self, [self.node.pk])])
153
141
  else:
154
142
  union_sql, union_params = base_sql, params
@@ -198,7 +186,7 @@ class TreeQuery:
198
186
  if include_self:
199
187
  sql_self = f"SELECT id, _depth, priority FROM {self.db_table} WHERE id = %s" # noqa: D501
200
188
  queries.append((sql_self, [self.node.pk]))
201
- combined_sql, combined_params = self.wrap_union_all(queries)
189
+ combined_sql, combined_params = SQLCompat.wrap_union_all(queries)
202
190
  combined_sql = self.order_by(combined_sql, "_depth, priority")
203
191
  return combined_sql, combined_params
204
192
 
@@ -2,24 +2,16 @@
2
2
  """
3
3
  TreeNode TaskQuery manager
4
4
 
5
- Version: 3.0.0
5
+ Version: 3.0.4
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
9
9
 
10
- from django.db import connection
10
+ import atexit
11
+ from django.db import connection, transaction
11
12
 
12
13
  from ..utils.db import TreePathCompiler
13
14
 
14
- '''
15
- try:
16
- profile
17
- except NameError:
18
- def profile(func):
19
- """Profile."""
20
- return func
21
- '''
22
-
23
15
 
24
16
  class TreeTaskQueue:
25
17
  """TreeTaskQueue Class."""
@@ -28,31 +20,93 @@ class TreeTaskQueue:
28
20
  """Init the task query."""
29
21
  self.model = model
30
22
  self.queue = []
23
+ self._running = False
24
+
25
+ # Register the execution queue when the interpreter exits
26
+ atexit.register(self._atexit_run)
27
+
28
+ def _atexit_run(self):
29
+ """Run queue on interpreter exit if pending tasks exist."""
30
+ if self.queue and not self._running:
31
+ try:
32
+ self.run()
33
+ except Exception as e:
34
+ # Don't crash on completion, just log
35
+ print(f"[TreeTaskQueue] Error during atexit: {e}")
31
36
 
32
37
  def add(self, mode, parent_id):
33
- """Add task to the query."""
38
+ """Add task to the queue.
39
+
40
+ Parameters:
41
+ mode (str): Task type (currently only "update").
42
+ parent_id (int|None): ID of parent node to update from (None = full tree).
43
+ """
34
44
  self.queue.append({"mode": mode, "parent_id": parent_id})
35
45
 
36
46
  def run(self):
37
- """Run task queue."""
47
+ """Run task queue.
48
+
49
+ This method collects all queued tasks, optimizes them, and performs
50
+ a recursive rebuild of tree paths and depths using SQL. Locks the
51
+ required rows before running.
52
+
53
+ Uses Django's `transaction.atomic()` to ensure that any recursive CTE
54
+ execution or SAVEPOINT creation works properly under PostgreSQL.
55
+ """
38
56
  if len(self.queue) == 0:
39
57
  return
58
+
59
+ self._running = True
40
60
  try:
41
61
  optimized = self._optimize()
42
- for task in optimized:
43
- if task["mode"] == "update":
44
- parent_id = task["parent_id"]
45
- TreePathCompiler.update_path(
46
- model=self.model,
47
- parent_id=parent_id
48
- )
62
+ if not optimized:
63
+ return
64
+
65
+ parent_ids = [t["parent_id"] for t in optimized if t["parent_id"] is not None]
66
+
67
+ with transaction.atomic():
68
+ if any(t["parent_id"] is None for t in optimized):
69
+ try:
70
+ with connection.cursor() as cursor:
71
+ cursor.execute(
72
+ f"SELECT id FROM {self.model._meta.db_table} WHERE parent_id IS NULL FOR UPDATE NOWAIT"
73
+ )
74
+ except Exception as e:
75
+ print(f"[TreeTaskQueue] Skipped (root locked): {e}")
76
+ return
77
+ else:
78
+ try:
79
+ with connection.cursor() as cursor:
80
+ for parent_id in parent_ids:
81
+ cursor.execute(
82
+ f"SELECT id FROM {self.model._meta.db_table} WHERE id = %s FOR UPDATE NOWAIT",
83
+ [parent_id],
84
+ )
85
+ except Exception as e:
86
+ print(f"[TreeTaskQueue] Skipped (parent locked): {e}")
87
+ return
88
+
89
+ for task in optimized:
90
+ if task["mode"] == "update":
91
+ TreePathCompiler.update_path(
92
+ model=self.model,
93
+ parent_id=task["parent_id"]
94
+ )
95
+
96
+ except Exception as e:
97
+ print(f"[TreeTaskQueue] Error in run: {e}")
98
+ connection.rollback()
49
99
  finally:
50
100
  self.queue.clear()
51
101
  self._running = False
52
102
 
53
- # @profile
54
103
  def _optimize(self):
55
- """Return optimized task queue (ID-only logic)."""
104
+ """Return optimized task queue (ID-only logic).
105
+
106
+ Attempts to merge redundant or overlapping subtree updates into
107
+ the minimal set of unique parent IDs that need to be rebuilt.
108
+ If it finds a common root, it returns a single task for full rebuild.
109
+ """
56
110
  result_set = set()
57
111
  id_set = set()
58
112
 
@@ -60,8 +114,6 @@ class TreeTaskQueue:
60
114
  if task["mode"] == "update":
61
115
  parent_id = task["parent_id"]
62
116
  if parent_id is None:
63
- # If we are already updating the entire tree, then
64
- # the remaining tasks are meaningless # noqa: D501
65
117
  return [{"mode": "update", "parent_id": None}]
66
118
  else:
67
119
  id_set.add(parent_id)
@@ -74,8 +126,6 @@ class TreeTaskQueue:
74
126
  for other in id_list[:]:
75
127
  ancestor = self._get_common_ancestor(current, other)
76
128
  if ancestor is not None:
77
- # If the common ancestor is the root, then we update
78
- # the entire tree
79
129
  if ancestor in self._get_root_ids():
80
130
  return [{"mode": "update", "parent_id": None}]
81
131
  if ancestor not in id_set:
@@ -87,36 +137,25 @@ class TreeTaskQueue:
87
137
  if not merged:
88
138
  result_set.add(current)
89
139
 
90
- return [{"mode": "update", "parent_id": pk} for pk in sorted(result_set)] # noqa: D501
140
+ return [{"mode": "update", "parent_id": pk} for pk in sorted(result_set)]
91
141
 
92
142
  def _get_root_ids(self):
93
143
  """Return root node IDs."""
94
144
  with connection.cursor() as cursor:
95
145
  cursor.execute(
96
- f"SELECT id FROM {self.model._meta.db_table} WHERE parent_id IS NULL") # noqa: D501
146
+ f"SELECT id FROM {self.model._meta.db_table} WHERE parent_id IS NULL")
97
147
  return [row[0] for row in cursor.fetchall()]
98
148
 
99
149
  def _get_parent_id(self, node_id):
100
150
  """Return parent ID for a given node."""
101
151
  with connection.cursor() as cursor:
102
152
  cursor.execute(
103
- f"SELECT parent_id FROM {self.model._meta.db_table} WHERE id = %s", [node_id]) # noqa: D501
153
+ f"SELECT parent_id FROM {self.model._meta.db_table} WHERE id = %s", [node_id])
104
154
  row = cursor.fetchone()
105
155
  return row[0] if row else None
106
156
 
107
- '''
108
- def _get_ancestor_path(self, node_id):
109
- """Return list of ancestor IDs including the node itself."""
110
- path = []
111
- while node_id is not None:
112
- path.append(node_id)
113
- node_id = self._get_parent_id(node_id)
114
- return path[::-1] # root to leaf
115
- '''
116
-
117
- # @profile
118
157
  def _get_ancestor_path(self, node_id):
119
- """Return list of ancestor IDs including the node itself, using recursive SQL.""" # noqa: D501
158
+ """Return list of ancestor IDs including the node itself, using recursive SQL."""
120
159
  table = self.model._meta.db_table
121
160
 
122
161
  sql = f"""
@@ -140,7 +179,6 @@ class TreeTaskQueue:
140
179
 
141
180
  return [row[0] for row in rows]
142
181
 
143
- # @profile
144
182
  def _get_common_ancestor(self, id1, id2):
145
183
  """Return common ancestor ID between two nodes."""
146
184
  path1 = self._get_ancestor_path(id1)