django-fast-treenode 3.0.7__tar.gz → 3.2.0__tar.gz
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.7/django_fast_treenode.egg-info → django_fast_treenode-3.2.0}/PKG-INFO +3 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/README.md +1 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0/django_fast_treenode.egg-info}/PKG-INFO +3 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/django_fast_treenode.egg-info/SOURCES.txt +31 -27
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/django_fast_treenode.egg-info/requires.txt +1 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/admin.md +1 -4
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/api.md +1 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/apifirst.md +2 -2
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/roadmap.md +1 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/pyproject.toml +2 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/setup.py +3 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/tests/test_suite.py +0 -4
- django_fast_treenode-3.2.0/treenode/admin/admin.py +222 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/admin/mixin.py +6 -6
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/managers/managers.py +1 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/managers/queries.py +1 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/managers/tasks.py +1 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/node.py +4 -4
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/models.py +14 -9
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/settings.py +4 -1
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/css/treenode_admin.css +21 -0
- django_fast_treenode-3.2.0/treenode/static/treenode/js/treenode_admin.js +322 -0
- django_fast_treenode-3.2.0/treenode/static/treenode/vendors/jquery-ui/.gitkeep +0 -0
- django_fast_treenode-3.2.0/treenode/templates/treenode/admin/treenode_changelist.html +71 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/templates/treenode/admin/treenode_import_export.html +3 -2
- django_fast_treenode-3.2.0/treenode/templates/treenode/admin/treenode_rows.html +54 -0
- django_fast_treenode-3.2.0/treenode/templatetags/__init__.py +0 -0
- django_fast_treenode-3.2.0/treenode/templatetags/treenode_admin.py +89 -0
- django_fast_treenode-3.2.0/treenode/utils/__init__.py +0 -0
- django_fast_treenode-3.2.0/treenode/utils/jwt_auth.py +25 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/version.py +2 -2
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/views/autoapi.py +5 -1
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/views/autocomplete.py +52 -52
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/views/children.py +41 -41
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/views/common.py +23 -23
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/widgets.py +2 -2
- django_fast_treenode-3.0.7/treenode/admin/admin.py +0 -244
- django_fast_treenode-3.0.7/treenode/static/js/treenode_admin.js +0 -531
- django_fast_treenode-3.0.7/treenode/templates/treenode/admin/treenode_changelist.html +0 -25
- django_fast_treenode-3.0.7/treenode/templates/treenode/admin/treenode_rows.html +0 -57
- django_fast_treenode-3.0.7/treenode/templatetags/treenode_admin.py +0 -46
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/LICENSE +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/MANIFEST.in +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/django_fast_treenode.egg-info/dependency_links.txt +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/django_fast_treenode.egg-info/top_level.txt +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/.gitignore +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/.nojekyll +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/about.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/cache.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/customization.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/dnd.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/import_export.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/index.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/insert-after.jpg +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/insert-as-child.jpg +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/installation.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/migration.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/models.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/requirements.txt +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/docs/using.md +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/setup.cfg +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/__init__.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/admin/__init__.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/admin/changelist.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/admin/exporter.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/admin/importer.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/apps.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/cache.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/forms.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/managers/__init__.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/__init__.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/decorators.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/factory.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/__init__.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/ancestors.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/children.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/descendants.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/family.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/logical.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/properties.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/roots.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/siblings.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/tree.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/models/mixins/update.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/signals.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/static/.gitkeep +0 -0
- /django_fast_treenode-3.0.7/treenode/templatetags/__init__.py → /django_fast_treenode-3.2.0/treenode/static/treenode/.gitkeep +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/css/.gitkeep +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/css/tree_widget.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/css/treenode_tabs.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/js/.gitkeep +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/js/lz-string.min.js +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/js/tree_widget.js +0 -0
- /django_fast_treenode-3.0.7/treenode/utils/__init__.py → /django_fast_treenode-3.2.0/treenode/static/treenode/vendors/.gitkeep +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/AUTHORS.txt +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/LICENSE.txt +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/external/jquery/jquery.js +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/images/ui-icons_444444_256x240.png +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/images/ui-icons_555555_256x240.png +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/images/ui-icons_777620_256x240.png +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/images/ui-icons_777777_256x240.png +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/index.html +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.js +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.min.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.min.js +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.structure.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.structure.min.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.theme.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/jquery-ui.theme.min.css +0 -0
- {django_fast_treenode-3.0.7/treenode/static → django_fast_treenode-3.2.0/treenode/static/treenode}/vendors/jquery-ui/package.json +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/templates/.gitkeep +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/templates/treenode/.gitkeep +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/templates/treenode/admin/.gitkeep +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/templates/treenode/admin/treenode_ajax_rows.html +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/templates/treenode/widgets/tree_widget.html +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/tests.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/urls.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/utils/db/__init__.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/utils/db/compiler.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/utils/db/db_vendor.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/utils/db/service.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/utils/db/sqlcompat.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/utils/db/sqlquery.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/views/__init__.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/views/crud.py +0 -0
- {django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/treenode/views/search.py +0 -0
{django_fast_treenode-3.0.7/django_fast_treenode.egg-info → django_fast_treenode-3.2.0}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: django-fast-treenode
|
3
|
-
Version: 3.0
|
3
|
+
Version: 3.2.0
|
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
|
@@ -57,6 +57,7 @@ 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
|
60
|
+
Requires-Dist: PyJWT>=2.0
|
60
61
|
Dynamic: author
|
61
62
|
Dynamic: home-page
|
62
63
|
Dynamic: license-file
|
@@ -126,6 +127,7 @@ At the moment, django-fast-treeenode is, if not the best, then one of the best p
|
|
126
127
|
- **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
128
|
- **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..
|
128
129
|
- **Lightweight**: All functionality is implemented within the package without heavyweight dependencies such as `djangorestframework` or `django-import-export`.
|
130
|
+
- **Optional JWT authentication**: enable token-based protection for the API with a single setting.
|
129
131
|
|
130
132
|
All this makes **Treenode Framework** a prime candidate for your needs.
|
131
133
|
|
@@ -62,6 +62,7 @@ At the moment, django-fast-treeenode is, if not the best, then one of the best p
|
|
62
62
|
- **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.
|
63
63
|
- **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..
|
64
64
|
- **Lightweight**: All functionality is implemented within the package without heavyweight dependencies such as `djangorestframework` or `django-import-export`.
|
65
|
+
- **Optional JWT authentication**: enable token-based protection for the API with a single setting.
|
65
66
|
|
66
67
|
All this makes **Treenode Framework** a prime candidate for your needs.
|
67
68
|
|
{django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0/django_fast_treenode.egg-info}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: django-fast-treenode
|
3
|
-
Version: 3.0
|
3
|
+
Version: 3.2.0
|
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
|
@@ -57,6 +57,7 @@ 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
|
60
|
+
Requires-Dist: PyJWT>=2.0
|
60
61
|
Dynamic: author
|
61
62
|
Dynamic: home-page
|
62
63
|
Dynamic: license-file
|
@@ -126,6 +127,7 @@ At the moment, django-fast-treeenode is, if not the best, then one of the best p
|
|
126
127
|
- **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
128
|
- **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..
|
128
129
|
- **Lightweight**: All functionality is implemented within the package without heavyweight dependencies such as `djangorestframework` or `django-import-export`.
|
130
|
+
- **Optional JWT authentication**: enable token-based protection for the API with a single setting.
|
129
131
|
|
130
132
|
All this makes **Treenode Framework** a prime candidate for your needs.
|
131
133
|
|
{django_fast_treenode-3.0.7 → django_fast_treenode-3.2.0}/django_fast_treenode.egg-info/SOURCES.txt
RENAMED
@@ -65,33 +65,36 @@ treenode/models/mixins/siblings.py
|
|
65
65
|
treenode/models/mixins/tree.py
|
66
66
|
treenode/models/mixins/update.py
|
67
67
|
treenode/static/.gitkeep
|
68
|
-
treenode/static/
|
69
|
-
treenode/static/
|
70
|
-
treenode/static/css/
|
71
|
-
treenode/static/css/
|
72
|
-
treenode/static/
|
73
|
-
treenode/static/
|
74
|
-
treenode/static/js/
|
75
|
-
treenode/static/js/
|
76
|
-
treenode/static/
|
77
|
-
treenode/static/vendors
|
78
|
-
treenode/static/vendors/jquery-ui
|
79
|
-
treenode/static/vendors/jquery-ui/
|
80
|
-
treenode/static/vendors/jquery-ui/
|
81
|
-
treenode/static/vendors/jquery-ui/
|
82
|
-
treenode/static/vendors/jquery-ui/jquery-ui.
|
83
|
-
treenode/static/vendors/jquery-ui/jquery-ui.
|
84
|
-
treenode/static/vendors/jquery-ui/jquery-ui.
|
85
|
-
treenode/static/vendors/jquery-ui/jquery-ui.
|
86
|
-
treenode/static/vendors/jquery-ui/jquery-ui.
|
87
|
-
treenode/static/vendors/jquery-ui/
|
88
|
-
treenode/static/vendors/jquery-ui/
|
89
|
-
treenode/static/vendors/jquery-ui/
|
90
|
-
treenode/static/vendors/jquery-ui/
|
91
|
-
treenode/static/vendors/jquery-ui/
|
92
|
-
treenode/static/vendors/jquery-ui/images/ui-
|
93
|
-
treenode/static/vendors/jquery-ui/images/ui-
|
94
|
-
treenode/static/vendors/jquery-ui/images/ui-
|
68
|
+
treenode/static/treenode/.gitkeep
|
69
|
+
treenode/static/treenode/css/.gitkeep
|
70
|
+
treenode/static/treenode/css/tree_widget.css
|
71
|
+
treenode/static/treenode/css/treenode_admin.css
|
72
|
+
treenode/static/treenode/css/treenode_tabs.css
|
73
|
+
treenode/static/treenode/js/.gitkeep
|
74
|
+
treenode/static/treenode/js/lz-string.min.js
|
75
|
+
treenode/static/treenode/js/tree_widget.js
|
76
|
+
treenode/static/treenode/js/treenode_admin.js
|
77
|
+
treenode/static/treenode/vendors/.gitkeep
|
78
|
+
treenode/static/treenode/vendors/jquery-ui/.gitkeep
|
79
|
+
treenode/static/treenode/vendors/jquery-ui/AUTHORS.txt
|
80
|
+
treenode/static/treenode/vendors/jquery-ui/LICENSE.txt
|
81
|
+
treenode/static/treenode/vendors/jquery-ui/index.html
|
82
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.css
|
83
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.js
|
84
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.min.css
|
85
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.min.js
|
86
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.structure.css
|
87
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.structure.min.css
|
88
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.theme.css
|
89
|
+
treenode/static/treenode/vendors/jquery-ui/jquery-ui.theme.min.css
|
90
|
+
treenode/static/treenode/vendors/jquery-ui/package.json
|
91
|
+
treenode/static/treenode/vendors/jquery-ui/external/jquery/jquery.js
|
92
|
+
treenode/static/treenode/vendors/jquery-ui/images/ui-icons_444444_256x240.png
|
93
|
+
treenode/static/treenode/vendors/jquery-ui/images/ui-icons_555555_256x240.png
|
94
|
+
treenode/static/treenode/vendors/jquery-ui/images/ui-icons_777620_256x240.png
|
95
|
+
treenode/static/treenode/vendors/jquery-ui/images/ui-icons_777777_256x240.png
|
96
|
+
treenode/static/treenode/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png
|
97
|
+
treenode/static/treenode/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png
|
95
98
|
treenode/templates/.gitkeep
|
96
99
|
treenode/templates/treenode/.gitkeep
|
97
100
|
treenode/templates/treenode/admin/.gitkeep
|
@@ -103,6 +106,7 @@ treenode/templates/treenode/widgets/tree_widget.html
|
|
103
106
|
treenode/templatetags/__init__.py
|
104
107
|
treenode/templatetags/treenode_admin.py
|
105
108
|
treenode/utils/__init__.py
|
109
|
+
treenode/utils/jwt_auth.py
|
106
110
|
treenode/utils/db/__init__.py
|
107
111
|
treenode/utils/db/compiler.py
|
108
112
|
treenode/utils/db/db_vendor.py
|
@@ -16,7 +16,6 @@ class CategoryAdmin(TreeNodeModelAdmin):
|
|
16
16
|
# Set the display mode: 'accordion', 'breadcrumbs', or 'indentation'
|
17
17
|
treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_ACCORDION
|
18
18
|
# treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_BREADCRUMBS
|
19
|
-
# treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_INDENTATION
|
20
19
|
|
21
20
|
list_display = ("name",)
|
22
21
|
search_fields = ("name",)
|
@@ -30,8 +29,6 @@ You can choose from three display modes:
|
|
30
29
|
Expands/collapses nodes dynamically.
|
31
30
|
- **`TREENODE_DISPLAY_MODE_BREADCRUMBS`**
|
32
31
|
Displays the tree as a sequence of **breadcrumbs**, making it easy to navigate.
|
33
|
-
- **`TREENODE_DISPLAY_MODE_INDENTATION`**
|
34
|
-
Uses a **long dash** (`———`) to indicate nesting levels, providing a simple visual structure.
|
35
32
|
|
36
33
|
The accordion mode is **always active**, and the setting only affects how nodes are displayed.
|
37
34
|
|
@@ -100,7 +97,7 @@ class CategorySelectionForm(forms.Form):
|
|
100
97
|
|
101
98
|
If you plan to use this widget in non-admin templates, make sure the necessary **JavaScript and CSS files** are included:
|
102
99
|
```html
|
103
|
-
<link rel="stylesheet" href="/static/treenode/tree_widget.css">
|
100
|
+
<link rel="stylesheet" href="/static/treenode/css/tree_widget.css">
|
104
101
|
<script src="/static/treenode/js/tree_widget.js"></script>
|
105
102
|
```
|
106
103
|
|
@@ -111,9 +111,9 @@ No complicated payloads. No custom formats. **TreeNode Framework** believes tha
|
|
111
111
|
### Basic Access Control
|
112
112
|
TreeNode Framework follows an API-First philosophy: API endpoints are generated automatically for each tree model, without the need to manually register views or routes.
|
113
113
|
|
114
|
-
|
114
|
+
By default, API protection uses Django's login sessions (`login_required`).
|
115
115
|
|
116
|
-
|
116
|
+
Starting from version 3.0.9 you can enable JWT authentication by setting `TREENODE_API_USE_JWT = True` in your project settings. In this mode the API expects an `Authorization: Bearer <token>` header with a token signed using your `SECRET_KEY`.
|
117
117
|
|
118
118
|
#### How to Secure Your API Step-by-Step
|
119
119
|
Since TreeNode Framework does not provide an authentication system itself, you need to set up basic login endpoints in your project.
|
@@ -15,7 +15,7 @@ The 3.x release series will focus on strengthening TreeNode Framework in terms o
|
|
15
15
|
- Provide a fallback auto-run mode for DEBUG environments (using `atexit` or thread-based handler).
|
16
16
|
- Ensure task queue consistency across multiple WSGI workers or scripts.
|
17
17
|
|
18
|
-
* **Version 3.2 — JWT Authentication for API**
|
18
|
+
* **Version 3.2 — JWT Authentication for API** *(implemented)*
|
19
19
|
|
20
20
|
- Introduce optional JWT-based token authentication for the auto-generated API.
|
21
21
|
- Allow easy activation through a single setting (`TREENODE_API_USE_JWT = True`).
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "django-fast-treenode"
|
7
|
-
version = "3.0
|
7
|
+
version = "3.2.0"
|
8
8
|
description = "Treenode Framework for supporting tree (hierarchical) data structure in Django projects"
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [{ name = "Timur Kady", email = "timurkady@yandex.com" }]
|
@@ -15,6 +15,7 @@ dependencies = [
|
|
15
15
|
'msgpack>=1.0.0',
|
16
16
|
'openpyxl>=3.0.0',
|
17
17
|
'pyyaml>=5.1',
|
18
|
+
'PyJWT>=2.0',
|
18
19
|
]
|
19
20
|
classifiers = [
|
20
21
|
'Development Status :: 5 - Production/Stable',
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='django-fast-treenode',
|
5
|
-
version='3.0
|
5
|
+
version='3.2.0',
|
6
6
|
description='Treenode Framework for supporting tree (hierarchical) data structure in Django projects',
|
7
7
|
long_description=open('README.md', encoding='utf-8').read(),
|
8
8
|
long_description_content_type='text/markdown',
|
@@ -18,6 +18,7 @@ setup(
|
|
18
18
|
'msgpack>=1.0.0',
|
19
19
|
'openpyxl>=3.0.0',
|
20
20
|
'pyyaml>=5.1',
|
21
|
+
'PyJWT>=2.0',
|
21
22
|
],
|
22
23
|
classifiers=[
|
23
24
|
'Development Status :: 5 - Production/Stable',
|
@@ -42,3 +43,4 @@ setup(
|
|
42
43
|
python_requires='>=3.9',
|
43
44
|
)
|
44
45
|
|
46
|
+
|
@@ -89,11 +89,7 @@ class TreeNodeModelTests(TestCase):
|
|
89
89
|
|
90
90
|
def test_delete_subtree(self):
|
91
91
|
self.a.delete(cascade=False)
|
92
|
-
|
93
|
-
tree_data = TestModel.get_tree_json()
|
94
|
-
|
95
92
|
self.root.check_tree_integrity()
|
96
93
|
qs = TestModel.objects.filter(pk__in=[self.a.pk, self.c.pk]).all()
|
97
|
-
|
98
94
|
self.assertFalse(TestModel.objects.filter(pk=self.a.pk).exists())
|
99
95
|
self.assertTrue(TestModel.objects.filter(pk=self.c.pk).exists())
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
TreeNode Admin Model Class
|
4
|
+
|
5
|
+
Modified admin panel for django-fast-treenode. Solves the following problems:
|
6
|
+
- Set list_per_page = 10000 to display all elements at once.
|
7
|
+
- Hidden standard pagination via CSS
|
8
|
+
- Disabled counting the total number of elements to speed up loading
|
9
|
+
- Accordion works regardless of the display mode
|
10
|
+
Two modes are supported:
|
11
|
+
- Indented - with indents and icons
|
12
|
+
- Breadcrumbs - with breadcrumbs
|
13
|
+
All modes have links to editing objects.
|
14
|
+
|
15
|
+
- Expand buttons for nodes with children
|
16
|
+
|
17
|
+
Additional features:
|
18
|
+
- Control panel with "Expand All" / "Collapse All" buttons
|
19
|
+
- Saving the state of the tree between page transitions
|
20
|
+
- Smooth animations when expanding/collapsing
|
21
|
+
- Counting the total number of nodes in the tree
|
22
|
+
- Recursive hiding of grandchildren when collapsing the parent
|
23
|
+
|
24
|
+
Version: 3.1.0
|
25
|
+
Author: Timur Kady
|
26
|
+
Email: timurkady@yandex.com
|
27
|
+
"""
|
28
|
+
|
29
|
+
|
30
|
+
from django.contrib import admin
|
31
|
+
from django.db import models
|
32
|
+
from django.urls import reverse
|
33
|
+
from django.utils.html import escape
|
34
|
+
from django.utils.safestring import mark_safe
|
35
|
+
from django.utils.translation import gettext_lazy as _
|
36
|
+
|
37
|
+
from .mixin import AdminMixin
|
38
|
+
from ..forms import TreeNodeForm
|
39
|
+
from ..widgets import TreeWidget
|
40
|
+
from .importer import TreeNodeImporter
|
41
|
+
from .exporter import TreeNodeExporter
|
42
|
+
|
43
|
+
import logging
|
44
|
+
|
45
|
+
logger = logging.getLogger(__name__)
|
46
|
+
|
47
|
+
|
48
|
+
class TreeNodeModelAdmin(AdminMixin, admin.ModelAdmin):
|
49
|
+
"""Admin for TreeNodeModel."""
|
50
|
+
|
51
|
+
# Режимы отображения
|
52
|
+
TREENODE_DISPLAY_MODE_ACCORDION = 'accordion'
|
53
|
+
TREENODE_DISPLAY_MODE_BREADCRUMBS = 'breadcrumbs'
|
54
|
+
treenode_display_mode = TREENODE_DISPLAY_MODE_ACCORDION
|
55
|
+
|
56
|
+
form = TreeNodeForm
|
57
|
+
importer_class = None
|
58
|
+
exporter_class = None
|
59
|
+
ordering = []
|
60
|
+
|
61
|
+
formfield_overrides = {
|
62
|
+
models.ForeignKey: {'widget': TreeWidget()},
|
63
|
+
}
|
64
|
+
|
65
|
+
change_list_template = "treenode/admin/treenode_changelist.html"
|
66
|
+
import_export = True
|
67
|
+
|
68
|
+
class Media:
|
69
|
+
"""Meta Class."""
|
70
|
+
|
71
|
+
css = {"all": (
|
72
|
+
"treenode/css/treenode_admin.css",
|
73
|
+
"treenode/vendors/jquery-ui/jquery-ui.css",
|
74
|
+
)}
|
75
|
+
js = (
|
76
|
+
"treenode/vendors/jquery-ui/jquery-ui.js",
|
77
|
+
# "treenode/js/lz-string.min.js",
|
78
|
+
"treenode/js/treenode_admin.js",
|
79
|
+
)
|
80
|
+
|
81
|
+
def __init__(self, model, admin_site):
|
82
|
+
"""Init method."""
|
83
|
+
super().__init__(model, admin_site)
|
84
|
+
|
85
|
+
if not self.list_display:
|
86
|
+
self.list_display = [field.name for field in model._meta.fields]
|
87
|
+
|
88
|
+
self.TreeNodeImporter = self.importer_class or TreeNodeImporter
|
89
|
+
self.TreeNodeExporter = self.exporter_class or TreeNodeExporter
|
90
|
+
|
91
|
+
def drag(self, obj):
|
92
|
+
"""Drag and drop сolumn."""
|
93
|
+
return mark_safe('<span class="treenode-drag-handle">☰</span>')
|
94
|
+
|
95
|
+
drag.short_description = _("Move")
|
96
|
+
|
97
|
+
def toggle(self, obj):
|
98
|
+
"""Toggle column."""
|
99
|
+
if obj.get_children_count() > 0:
|
100
|
+
return mark_safe(
|
101
|
+
f'<button class="treenode-toggle" data-node-id="{obj.pk}">►</button>' # noqa
|
102
|
+
)
|
103
|
+
return mark_safe('<div class="treenode-space"> </div>')
|
104
|
+
|
105
|
+
toggle.short_description = _("Expand")
|
106
|
+
|
107
|
+
def get_changelist(self, request, **kwargs):
|
108
|
+
"""Get changelist."""
|
109
|
+
ChangeList = super().get_changelist(request, **kwargs)
|
110
|
+
|
111
|
+
class NoPaginationChangeList(ChangeList):
|
112
|
+
"""Suppress pagination."""
|
113
|
+
|
114
|
+
def get_results(self, request):
|
115
|
+
"""Get result."""
|
116
|
+
super().get_results(request)
|
117
|
+
self.paginator.show_all = True
|
118
|
+
self.result_count = len(self.result_list)
|
119
|
+
self.full_result_count = len(self.result_list)
|
120
|
+
self.can_show_all = False
|
121
|
+
self.multi_page = False
|
122
|
+
self.actions = self.model_admin.get_actions(request)
|
123
|
+
|
124
|
+
return NoPaginationChangeList
|
125
|
+
|
126
|
+
def get_changelist_instance(self, request):
|
127
|
+
"""
|
128
|
+
Get changelist instance.
|
129
|
+
|
130
|
+
Make sure our custom ChangeList is used without pagination.
|
131
|
+
"""
|
132
|
+
ChangeList = self.get_changelist(request)
|
133
|
+
|
134
|
+
return ChangeList(
|
135
|
+
request,
|
136
|
+
self.model,
|
137
|
+
self.get_list_display(request),
|
138
|
+
self.get_list_display_links(
|
139
|
+
request,
|
140
|
+
self.get_list_display(request)
|
141
|
+
),
|
142
|
+
self.get_list_filter(request),
|
143
|
+
self.date_hierarchy,
|
144
|
+
self.search_fields,
|
145
|
+
self.list_select_related,
|
146
|
+
self.list_per_page,
|
147
|
+
self.list_max_show_all,
|
148
|
+
self.list_editable,
|
149
|
+
self,
|
150
|
+
sortable_by=self.get_sortable_by(request),
|
151
|
+
search_help_text=self.get_search_help_text(request),
|
152
|
+
)
|
153
|
+
|
154
|
+
def get_queryset(self, request):
|
155
|
+
"""Get queryset."""
|
156
|
+
qs = super().get_queryset(request)
|
157
|
+
return qs.select_related('parent')\
|
158
|
+
.prefetch_related('children')\
|
159
|
+
.order_by('_path')
|
160
|
+
|
161
|
+
def get_list_display(self, request):
|
162
|
+
"""Get list_display."""
|
163
|
+
def treenode_field(obj):
|
164
|
+
return self._get_treenode_field_display(request, obj)
|
165
|
+
|
166
|
+
description = str(self.model._meta.verbose_name)
|
167
|
+
treenode_field.short_description = description
|
168
|
+
|
169
|
+
return (self.drag, self.toggle, treenode_field)
|
170
|
+
|
171
|
+
def get_form(self, request, obj=None, **kwargs):
|
172
|
+
"""Get Form method."""
|
173
|
+
form = super().get_form(request, obj, **kwargs)
|
174
|
+
if "parent" in form.base_fields:
|
175
|
+
form.base_fields["parent"].widget = TreeWidget()
|
176
|
+
return form
|
177
|
+
|
178
|
+
def get_search_fields(self, request):
|
179
|
+
"""Get search fields."""
|
180
|
+
return [getattr(self.model, 'display_field', 'id') or 'id']
|
181
|
+
|
182
|
+
def _get_treenode_field_display(self, request, obj):
|
183
|
+
"""
|
184
|
+
Generate HTML to display tree nodes.
|
185
|
+
|
186
|
+
Depending on the selected display mode (accordion or breadcrumbs),
|
187
|
+
do the following:
|
188
|
+
- For accordion mode: add indents and icons.
|
189
|
+
- For breadcrumbs mode: display breadcrumb path.
|
190
|
+
"""
|
191
|
+
level = obj.get_depth()
|
192
|
+
display_field = getattr(obj, "display_field", None)
|
193
|
+
edit_url = reverse(
|
194
|
+
f"admin:{obj._meta.app_label}_{obj._meta.model_name}_change",
|
195
|
+
args=[obj.pk]
|
196
|
+
)
|
197
|
+
icon = ""
|
198
|
+
text = ""
|
199
|
+
padding = ""
|
200
|
+
closing = ""
|
201
|
+
|
202
|
+
if self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_ACCORDION:
|
203
|
+
icon = "📄 " if obj.is_leaf() else "📁 "
|
204
|
+
text = getattr(obj, display_field, str(obj))
|
205
|
+
padding = f'<span style="padding-left: {level * 1.5}em;">'
|
206
|
+
closing = "</span>"
|
207
|
+
elif self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_BREADCRUMBS: # noqa
|
208
|
+
if display_field:
|
209
|
+
breadcrumbs = obj.get_breadcrumbs(attr=display_field)
|
210
|
+
else:
|
211
|
+
breadcrumbs = [str(item) for item in obj.get_ancestors()]
|
212
|
+
|
213
|
+
text = "/" + "/".join([escape(label) for label in breadcrumbs])
|
214
|
+
|
215
|
+
content = f'{padding}{icon}<a href="{edit_url}">{escape(text)}</a>{closing}' # noqa
|
216
|
+
return mark_safe(content)
|
217
|
+
|
218
|
+
def get_list_per_page(self, request):
|
219
|
+
"""Get list per page."""
|
220
|
+
return 999999
|
221
|
+
|
222
|
+
# The End
|
@@ -62,7 +62,7 @@ class AdminMixin(admin.ModelAdmin):
|
|
62
62
|
return custom_urls + default_urls
|
63
63
|
|
64
64
|
def render_changelist_rows(self, objs: list, request):
|
65
|
-
"""
|
65
|
+
"""Render rows for insert into changelist."""
|
66
66
|
list_display = list(self.get_list_display(request))
|
67
67
|
checkbox_field_name = ACTION_CHECKBOX_NAME
|
68
68
|
if checkbox_field_name not in list_display:
|
@@ -227,7 +227,7 @@ class AdminMixin(admin.ModelAdmin):
|
|
227
227
|
|
228
228
|
def import_view(self, request):
|
229
229
|
"""
|
230
|
-
|
230
|
+
Import View.
|
231
231
|
|
232
232
|
Handles file upload and initiates import processing via
|
233
233
|
TreeNodeImporter.
|
@@ -242,14 +242,14 @@ class AdminMixin(admin.ModelAdmin):
|
|
242
242
|
extension = os.path.splitext(filename)[1].lower().lstrip('.')
|
243
243
|
if extension not in ['csv', 'tsv', 'json', 'xlsx', 'yaml']:
|
244
244
|
return JsonResponse(
|
245
|
-
{"error": _(f"Invalid file format ({extension}.")},
|
245
|
+
{"error": _(f"Invalid file format ({extension}).")},
|
246
246
|
status=200
|
247
247
|
)
|
248
248
|
importer = self.TreeNodeImporter(self.model, file, extension)
|
249
249
|
importer.parse()
|
250
250
|
result = importer.import_tree()
|
251
251
|
|
252
|
-
return render(request, "admin/treenode_import_export.html", {
|
252
|
+
return render(request, "treenode/admin/treenode_import_export.html", {
|
253
253
|
"created_count": result.get("created", 0),
|
254
254
|
"updated_count": result.get("updated", 0),
|
255
255
|
"errors": result.get("errors", []),
|
@@ -258,7 +258,7 @@ class AdminMixin(admin.ModelAdmin):
|
|
258
258
|
|
259
259
|
return render(
|
260
260
|
request,
|
261
|
-
"admin/treenode_import_export.html",
|
261
|
+
"treenode/admin/treenode_import_export.html",
|
262
262
|
{"import_active": True}
|
263
263
|
)
|
264
264
|
|
@@ -284,7 +284,7 @@ class AdminMixin(admin.ModelAdmin):
|
|
284
284
|
|
285
285
|
return render(
|
286
286
|
request,
|
287
|
-
"admin/treenode_import_export.html",
|
287
|
+
"treenode/admin/treenode_import_export.html",
|
288
288
|
{"import_active": False}
|
289
289
|
)
|
290
290
|
|
@@ -207,7 +207,7 @@ class TreeNodeManager(models.Manager):
|
|
207
207
|
|
208
208
|
WARNING: Unsafe low-level update bypassing all TreeNode protections.
|
209
209
|
Use only when bypassing _path/_depth/priority safety checks is
|
210
|
-
|
210
|
+
intentional.
|
211
211
|
"""
|
212
212
|
return models.QuerySet(self.model, using=self.db)\
|
213
213
|
.bulk_update(*args, **kwargs)
|
@@ -5,7 +5,7 @@ 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.
|
8
|
+
Version: 3.0.0
|
9
9
|
Author: Timur Kady
|
10
10
|
Email: timurkady@yandex.com
|
11
11
|
"""
|
@@ -40,18 +40,18 @@ class TreeNodeNodeMixin(models.Model):
|
|
40
40
|
self.refresh()
|
41
41
|
return self._depth
|
42
42
|
|
43
|
-
def distance_to(self,
|
43
|
+
def distance_to(self, target):
|
44
44
|
"""Return number of edges on shortest path between two nodes."""
|
45
45
|
self_path = self.query(objects='ancestors')
|
46
|
-
|
46
|
+
target_path = target.query(objects='ancestors')
|
47
47
|
|
48
48
|
i = 0
|
49
|
-
for a, b in zip(self_path,
|
49
|
+
for a, b in zip(self_path, target_path):
|
50
50
|
if a != b:
|
51
51
|
break
|
52
52
|
i += 1
|
53
53
|
|
54
|
-
return (len(self_path) - i) + (len(
|
54
|
+
return (len(self_path) - i) + (len(target_path) - i)
|
55
55
|
|
56
56
|
def get_index(self):
|
57
57
|
"""Get the node index (self, index in node.parent.children list)."""
|
@@ -14,12 +14,12 @@ Features:
|
|
14
14
|
- Provides a caching mechanism to optimize performance.
|
15
15
|
- Includes methods for tree traversal, manipulation, and serialization.
|
16
16
|
|
17
|
+
With full support for SQL queues, deferred execution,
|
18
|
+
custom sorting, and a sleek architecture without unnecessary duplication.
|
19
|
+
|
17
20
|
Version: 3.0.7
|
18
21
|
Author: Timur Kady
|
19
22
|
Email: timurkady@yandex.com
|
20
|
-
|
21
|
-
Причём с абсолютной поддержкой SQL-очередей, deferred execution,
|
22
|
-
кастомной сортировки и крутой архитектурой без лишнего дублирования.
|
23
23
|
"""
|
24
24
|
|
25
25
|
from __future__ import annotations
|
@@ -38,6 +38,8 @@ from ..cache import treenode_cache as cache
|
|
38
38
|
from ..settings import SEGMENT_LENGTH, BASE
|
39
39
|
from ..signals import disable_signals
|
40
40
|
|
41
|
+
import logging
|
42
|
+
logger = logging.getLogger(__name__)
|
41
43
|
|
42
44
|
class TreeNodeModel(
|
43
45
|
mx.TreeNodeAncestorsMixin, mx.TreeNodeChildrenMixin,
|
@@ -156,10 +158,10 @@ class TreeNodeModel(
|
|
156
158
|
# Update subtree
|
157
159
|
self._update_path(self.parent_id)
|
158
160
|
self.sqlq.flush()
|
159
|
-
#
|
161
|
+
# Clear cache
|
160
162
|
self.clear_cache()
|
161
163
|
|
162
|
-
# Saving and
|
164
|
+
# Saving and Updating methods ----------------------------------
|
163
165
|
|
164
166
|
def save(self, *args, **kwargs):
|
165
167
|
"""
|
@@ -193,7 +195,10 @@ class TreeNodeModel(
|
|
193
195
|
if is_move:
|
194
196
|
self._meta.model.tasks.add("update", state["parent_id"])
|
195
197
|
else:
|
196
|
-
|
198
|
+
logger.error(
|
199
|
+
"TreeNodeModel save error: object with pk %s not found in DB",
|
200
|
+
self.pk,
|
201
|
+
)
|
197
202
|
else:
|
198
203
|
is_new = True
|
199
204
|
|
@@ -335,11 +340,11 @@ class TreeNodeModel(
|
|
335
340
|
queue.extend(model.objects.filter(parent=node))
|
336
341
|
|
337
342
|
if verbose and errors:
|
338
|
-
|
343
|
+
logger.error("Tree integrity check failed:")
|
339
344
|
for err in errors:
|
340
|
-
|
345
|
+
logger.error(" - %s", err)
|
341
346
|
elif verbose:
|
342
|
-
|
347
|
+
logger.info("Tree integrity: OK ✅")
|
343
348
|
|
344
349
|
return errors
|
345
350
|
|
@@ -18,11 +18,14 @@ SEGMENT_LENGTH = getattr(settings, "TREENODE_SEGMENT_LENGTH", 3)
|
|
18
18
|
# Serialization dictionary: hexadecimal encoding, fixed segment size
|
19
19
|
SEGMENT_BASE = 16
|
20
20
|
|
21
|
-
#
|
21
|
+
# Number of children per tree node
|
22
22
|
BASE = SEGMENT_BASE ** SEGMENT_LENGTH # 4096
|
23
23
|
|
24
24
|
|
25
25
|
TREENODE_PAD_CHAR = getattr(settings, "TREENODE_PAD_CHAR", "'0'")
|
26
26
|
|
27
|
+
# Optional JWT protection for API endpoints
|
28
|
+
API_USE_JWT = getattr(settings, "TREENODE_API_USE_JWT", False)
|
29
|
+
|
27
30
|
|
28
31
|
# The End
|