django-fast-treenode 3.0.1__py3-none-any.whl → 3.0.3__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 (26) hide show
  1. {django_fast_treenode-3.0.1.dist-info → django_fast_treenode-3.0.3.dist-info}/METADATA +24 -24
  2. {django_fast_treenode-3.0.1.dist-info → django_fast_treenode-3.0.3.dist-info}/RECORD +26 -26
  3. {django_fast_treenode-3.0.1.dist-info → django_fast_treenode-3.0.3.dist-info}/WHEEL +1 -1
  4. {django_fast_treenode-3.0.1.dist-info → django_fast_treenode-3.0.3.dist-info}/licenses/LICENSE +1 -0
  5. treenode/admin/admin.py +1 -0
  6. treenode/managers/queries.py +43 -0
  7. treenode/models/mixins/descendants.py +17 -11
  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/compiler.py +24 -25
  22. treenode/utils/db/sqlcompat.py +91 -6
  23. treenode/version.py +2 -3
  24. treenode/views/crud.py +31 -10
  25. treenode/widgets.py +2 -0
  26. {django_fast_treenode-3.0.1.dist-info → django_fast_treenode-3.0.3.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
- /*! jQuery UI - v1.14.1 - 2025-04-13
2
- * https://jqueryui.com
3
- * Copyright OpenJS Foundation and other contributors; Licensed MIT */
4
-
1
+ /*! jQuery UI - v1.14.1 - 2025-04-13
2
+ * https://jqueryui.com
3
+ * Copyright OpenJS Foundation and other contributors; Licensed MIT */
4
+
5
5
  .ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;background-image:none}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank.ui-icon-blank.ui-icon-blank{background-image:none}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3}.ui-widget-shadow{box-shadow:0 0 5px #666}
@@ -1,82 +1,82 @@
1
- {
2
- "name": "jquery-ui",
3
- "title": "jQuery UI",
4
- "description": "A curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library.",
5
- "version": "1.14.1",
6
- "homepage": "https://jqueryui.com",
7
- "author": {
8
- "name": "OpenJS Foundation and other contributors",
9
- "url": "https://github.com/jquery/jquery-ui/blob/1.14.1/AUTHORS.txt"
10
- },
11
- "main": "ui/widget.js",
12
- "maintainers": [
13
- {
14
- "name": "Jörn Zaefferer",
15
- "email": "joern.zaefferer@gmail.com",
16
- "url": "https://bassistance.de"
17
- },
18
- {
19
- "name": "Mike Sherov",
20
- "email": "mike.sherov@gmail.com",
21
- "url": "https://mike.sherov.com"
22
- },
23
- {
24
- "name": "TJ VanToll",
25
- "email": "tj.vantoll@gmail.com",
26
- "url": "https://www.tjvantoll.com"
27
- },
28
- {
29
- "name": "Felix Nagel",
30
- "email": "info@felixnagel.com",
31
- "url": "https://www.felixnagel.com"
32
- },
33
- {
34
- "name": "Alex Schmitz",
35
- "email": "arschmitz@gmail.com",
36
- "url": "https://github.com/arschmitz"
37
- }
38
- ],
39
- "repository": {
40
- "type": "git",
41
- "url": "git://github.com/jquery/jquery-ui.git"
42
- },
43
- "bugs": {
44
- "url": "https://github.com/jquery/jquery-ui/issues"
45
- },
46
- "license": "MIT",
47
- "scripts": {
48
- "build": "grunt build",
49
- "lint": "grunt lint",
50
- "test:server": "node tests/runner/server.js",
51
- "test:unit": "node tests/runner/command.js",
52
- "test": "grunt && npm run test:unit -- -h"
53
- },
54
- "dependencies": {
55
- "jquery": ">=1.12.0 <5.0.0"
56
- },
57
- "devDependencies": {
58
- "body-parser": "1.20.3",
59
- "browserstack-local": "1.5.5",
60
- "commitplease": "3.2.0",
61
- "diff": "5.2.0",
62
- "eslint-config-jquery": "3.0.2",
63
- "exit-hook": "4.0.0",
64
- "express": "4.21.1",
65
- "express-body-parser-error-handler": "1.0.7",
66
- "grunt": "1.6.1",
67
- "grunt-bowercopy": "1.2.5",
68
- "grunt-compare-size": "0.4.2",
69
- "grunt-contrib-concat": "2.1.0",
70
- "grunt-contrib-csslint": "2.0.0",
71
- "grunt-contrib-requirejs": "1.0.0",
72
- "grunt-contrib-uglify": "5.2.2",
73
- "grunt-eslint": "24.0.1",
74
- "grunt-git-authors": "3.2.0",
75
- "grunt-html": "17.1.0",
76
- "load-grunt-tasks": "5.1.0",
77
- "rimraf": "6.0.1",
78
- "selenium-webdriver": "4.26.0",
79
- "yargs": "17.7.2"
80
- },
81
- "keywords": []
82
- }
1
+ {
2
+ "name": "jquery-ui",
3
+ "title": "jQuery UI",
4
+ "description": "A curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library.",
5
+ "version": "1.14.1",
6
+ "homepage": "https://jqueryui.com",
7
+ "author": {
8
+ "name": "OpenJS Foundation and other contributors",
9
+ "url": "https://github.com/jquery/jquery-ui/blob/1.14.1/AUTHORS.txt"
10
+ },
11
+ "main": "ui/widget.js",
12
+ "maintainers": [
13
+ {
14
+ "name": "Jörn Zaefferer",
15
+ "email": "joern.zaefferer@gmail.com",
16
+ "url": "https://bassistance.de"
17
+ },
18
+ {
19
+ "name": "Mike Sherov",
20
+ "email": "mike.sherov@gmail.com",
21
+ "url": "https://mike.sherov.com"
22
+ },
23
+ {
24
+ "name": "TJ VanToll",
25
+ "email": "tj.vantoll@gmail.com",
26
+ "url": "https://www.tjvantoll.com"
27
+ },
28
+ {
29
+ "name": "Felix Nagel",
30
+ "email": "info@felixnagel.com",
31
+ "url": "https://www.felixnagel.com"
32
+ },
33
+ {
34
+ "name": "Alex Schmitz",
35
+ "email": "arschmitz@gmail.com",
36
+ "url": "https://github.com/arschmitz"
37
+ }
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git://github.com/jquery/jquery-ui.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/jquery/jquery-ui/issues"
45
+ },
46
+ "license": "MIT",
47
+ "scripts": {
48
+ "build": "grunt build",
49
+ "lint": "grunt lint",
50
+ "test:server": "node tests/runner/server.js",
51
+ "test:unit": "node tests/runner/command.js",
52
+ "test": "grunt && npm run test:unit -- -h"
53
+ },
54
+ "dependencies": {
55
+ "jquery": ">=1.12.0 <5.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "body-parser": "1.20.3",
59
+ "browserstack-local": "1.5.5",
60
+ "commitplease": "3.2.0",
61
+ "diff": "5.2.0",
62
+ "eslint-config-jquery": "3.0.2",
63
+ "exit-hook": "4.0.0",
64
+ "express": "4.21.1",
65
+ "express-body-parser-error-handler": "1.0.7",
66
+ "grunt": "1.6.1",
67
+ "grunt-bowercopy": "1.2.5",
68
+ "grunt-compare-size": "0.4.2",
69
+ "grunt-contrib-concat": "2.1.0",
70
+ "grunt-contrib-csslint": "2.0.0",
71
+ "grunt-contrib-requirejs": "1.0.0",
72
+ "grunt-contrib-uglify": "5.2.2",
73
+ "grunt-eslint": "24.0.1",
74
+ "grunt-git-authors": "3.2.0",
75
+ "grunt-html": "17.1.0",
76
+ "load-grunt-tasks": "5.1.0",
77
+ "rimraf": "6.0.1",
78
+ "selenium-webdriver": "4.26.0",
79
+ "yargs": "17.7.2"
80
+ },
81
+ "keywords": []
82
+ }
@@ -5,7 +5,7 @@ Tree update task compiler class.
5
5
  Compiles tasks to low-level SQL to update the materialized path (_path), depth
6
6
  (_depth), and node order (priority) when they are shifted or moved.
7
7
 
8
- Version: 3.0.0
8
+ Version: 3.1.0
9
9
  Author: Timur Kady
10
10
  Email: timurkady@yandex.com
11
11
  """
@@ -34,9 +34,11 @@ class TreePathCompiler:
34
34
  _depth) are recalculated.
35
35
  """
36
36
  db_table = model._meta.db_table
37
+ # Will eliminate the risk if the user names the model order or user.
38
+ qname = connection.ops.quote_name(db_table)
37
39
 
38
40
  sorting_field = model.sorting_field
39
- sorting_fields = ["priority", "id"] if sorting_field == "priority" else [sorting_field] # noqa: D501
41
+ sorting_fields = ["priority", "id"] if sorting_field == "priority" else [sorting_field] # noqa: D5017
40
42
  sort_expr = ", ".join([
41
43
  f"c.{field}" if "." not in field else field
42
44
  for field in sorting_fields
@@ -44,7 +46,7 @@ class TreePathCompiler:
44
46
 
45
47
  cte_header = "(id, parent_id, new_priority, new_path, new_depth)"
46
48
 
47
- row_number_expr = "ROW_NUMBER() OVER (ORDER BY {sort_expr}) - 1"
49
+ row_number_expr = f"ROW_NUMBER() OVER (ORDER BY {sort_expr}) - 1"
48
50
  hex_expr = SQLCompat.to_hex(row_number_expr)
49
51
  lpad_expr = SQLCompat.lpad(hex_expr, SEGMENT_LENGTH, "'0'")
50
52
 
@@ -57,7 +59,7 @@ class TreePathCompiler:
57
59
  {row_number_expr} AS new_priority,
58
60
  {new_path_expr} AS new_path,
59
61
  0 AS new_depth
60
- FROM {db_table} AS c
62
+ FROM {qname} AS c
61
63
  WHERE c.parent_id IS NULL
62
64
  """
63
65
  params = []
@@ -70,16 +72,18 @@ class TreePathCompiler:
70
72
  {row_number_expr} AS new_priority,
71
73
  {path_expr} AS new_path,
72
74
  p._depth + 1 AS new_depth
73
- FROM {db_table} c
74
- JOIN {db_table} p ON c.parent_id = p.id
75
+ FROM {qname} c
76
+ JOIN {qname} p ON c.parent_id = p.id
75
77
  WHERE p.id = %s
76
78
  """
77
79
  params = [parent_id]
78
80
 
79
- recursive_row_number_expr = "ROW_NUMBER() OVER (PARTITION BY c.parent_id ORDER BY {sort_expr}) - 1" # noqa: D501
81
+ recursive_row_number_expr = f"ROW_NUMBER() OVER (PARTITION BY c.parent_id ORDER BY {sort_expr}) - 1"
80
82
  recursive_hex_expr = SQLCompat.to_hex(recursive_row_number_expr)
81
- recursive_lpad_expr = SQLCompat.lpad(recursive_hex_expr, SEGMENT_LENGTH, "'0'") # noqa: D501
82
- recursive_path_expr = SQLCompat.concat("t.new_path", "'.'", recursive_lpad_expr) # noqa: D501
83
+ recursive_lpad_expr = SQLCompat.lpad(
84
+ recursive_hex_expr, SEGMENT_LENGTH, "'0'")
85
+ recursive_path_expr = SQLCompat.concat(
86
+ "t.new_path", "'.'", recursive_lpad_expr)
83
87
 
84
88
  recursive_sql = f"""
85
89
  SELECT
@@ -88,27 +92,22 @@ class TreePathCompiler:
88
92
  {recursive_row_number_expr} AS new_priority,
89
93
  {recursive_path_expr} AS new_path,
90
94
  t.new_depth + 1 AS new_depth
91
- FROM {db_table} c
95
+ FROM {qname} c
92
96
  JOIN tree_cte t ON c.parent_id = t.id
93
97
  """
94
98
 
95
- final_sql = f"""
96
- WITH RECURSIVE tree_cte {cte_header} AS (
97
- {base_sql}
98
- UNION ALL
99
- {recursive_sql}
100
- )
101
- UPDATE {db_table} AS orig
102
- SET
103
- priority = t.new_priority,
104
- _path = t.new_path,
105
- _depth = t.new_depth
106
- FROM tree_cte t
107
- WHERE orig.id = t.id;
108
- """
99
+ final_sql = SQLCompat.update_from(
100
+ db_table=db_table,
101
+ cte_header=cte_header,
102
+ base_sql=base_sql,
103
+ recursive_sql=recursive_sql,
104
+ update_fields=["priority", "_path", "_depth"]
105
+ )
109
106
 
110
107
  with connection.cursor() as cursor:
111
- cursor.execute(final_sql.format(sort_expr=sort_expr), params)
108
+ # Make params read-only
109
+ params = tuple(params)
110
+ cursor.execute(final_sql, params)
112
111
 
113
112
 
114
113
  # The End
@@ -17,11 +17,12 @@ Instead of LPAD(...)
17
17
  old: LPAD(...)
18
18
  new: SQLCompat.lpad(...)
19
19
 
20
- Version: 3.0.0
20
+ Version: 3.1.0
21
21
  Author: Timur Kady
22
22
  Email: timurkady@yandex.com
23
23
  """
24
24
 
25
+ from django.db import connection
25
26
  from .db_vendor import is_mysql, is_mariadb, is_sqlite, is_mssql
26
27
  from ...settings import TREENODE_PAD_CHAR
27
28
 
@@ -42,19 +43,103 @@ class SQLCompat:
42
43
 
43
44
  @staticmethod
44
45
  def to_hex(value):
45
- """Convert integer to hexadecimal string."""
46
+ """Convert integer to uppercase hexadecimal string."""
46
47
  if is_sqlite():
47
- return f"printf('%x', {value})"
48
+ return f"UPPER(printf('%x', {value}))"
49
+ elif is_mysql() or is_mariadb():
50
+ return f"UPPER(CONV({value}, 10, 16))"
48
51
  else:
49
- return f"TO_HEX({value})"
52
+ return f"UPPER(TO_HEX({value}))"
50
53
 
51
54
  @staticmethod
52
55
  def lpad(value, length, char=TREENODE_PAD_CHAR):
53
56
  """Pad string to the specified length."""
54
57
  if is_sqlite():
55
- return (f"substr(replace(hex(zeroblob({length})), '00', {char}), "
56
- f"1, {length} - length({value})) || {value}")
58
+ return f"printf('%0{length}s', {value})"
57
59
  else:
58
60
  return f"LPAD({value}, {length}, {char})"
59
61
 
62
+ @staticmethod
63
+ def update_from(db_table, cte_header, base_sql, recursive_sql, update_fields):
64
+ """
65
+ Generate final SQL for updating via recursive CTE.
66
+
67
+ PostgreSQL uses UPDATE ... FROM.
68
+ Other engines use vendor-specific strategies.
69
+ """
70
+ qt = connection.ops.quote_name(db_table)
71
+ def qf(f): return connection.ops.quote_name(f)
72
+
73
+ cte_alias = {
74
+ "priority": "new_priority",
75
+ "_path": "new_path",
76
+ "_depth": "new_depth",
77
+ }
78
+
79
+ if connection.vendor == "postgresql":
80
+ set_clause = ", ".join(
81
+ f"{qf(f)} = t.{cte_alias.get(f, f)}" for f in update_fields
82
+ )
83
+ return f"""
84
+ WITH RECURSIVE tree_cte {cte_header} AS (
85
+ {base_sql}
86
+ UNION ALL
87
+ {recursive_sql}
88
+ )
89
+ UPDATE {qt} AS orig
90
+ SET {set_clause}
91
+ FROM tree_cte t
92
+ WHERE orig.id = t.id;
93
+ """
94
+
95
+ elif connection.vendor in {"microsoft", "mssql"}:
96
+ set_clause = ", ".join(
97
+ f"{qt}.{f} = t.{f}" for f in update_fields
98
+ )
99
+ return f"""
100
+ WITH tree_cte {cte_header} AS (
101
+ {base_sql}
102
+ UNION ALL
103
+ {recursive_sql}
104
+ )
105
+ UPDATE orig
106
+ SET {set_clause}
107
+ FROM {qt} AS orig
108
+ JOIN tree_cte t ON orig.id = t.id;
109
+ """
110
+
111
+ elif connection.vendor == "oracle":
112
+ set_clause = ", ".join(
113
+ f"orig.{f} = t.{f}" for f in update_fields
114
+ )
115
+ return f"""
116
+ WITH tree_cte {cte_header} AS (
117
+ {base_sql}
118
+ UNION ALL
119
+ {recursive_sql}
120
+ )
121
+ MERGE INTO {qt} orig
122
+ USING tree_cte t
123
+ ON (orig.id = t.id)
124
+ WHEN MATCHED THEN UPDATE SET
125
+ {set_clause};
126
+ """
127
+
128
+ else:
129
+ set_clause = ", ".join(
130
+ f"{qf(f)} = (SELECT t.{f} FROM tree_cte t WHERE t.id = {qt}.id)"
131
+ for f in update_fields
132
+ )
133
+ where_clause = f"id IN (SELECT id FROM tree_cte)"
134
+ return f"""
135
+ WITH RECURSIVE tree_cte {cte_header} AS (
136
+ {base_sql}
137
+ UNION ALL
138
+ {recursive_sql}
139
+ )
140
+ UPDATE {qt}
141
+ SET {set_clause}
142
+ WHERE {where_clause};
143
+ """
144
+
60
145
  # The End
treenode/version.py CHANGED
@@ -4,10 +4,9 @@ TreeNode Version Module
4
4
 
5
5
  This module defines the current version of the TreeNode package.
6
6
 
7
- Version: 3.0.1
7
+ Version: 3.0.3
8
8
  Author: Timur Kady
9
9
  Email: timurkady@yandex.com
10
10
  """
11
11
 
12
-
13
- __version__ = '3.0.1'
12
+ __version__ = '3.0.3'
treenode/views/crud.py CHANGED
@@ -10,14 +10,17 @@ Email: timurkady@yandex.com
10
10
  """
11
11
 
12
12
  import json
13
-
13
+ import logging
14
+ from django.core.exceptions import ValidationError
14
15
  from django.forms.models import model_to_dict
15
16
  from django.http import (
16
17
  JsonResponse, HttpResponseBadRequest, HttpResponseNotFound,
17
18
  )
18
-
19
+ from django.utils.translation import gettext_lazy as _
19
20
  from django.views import View
20
21
 
22
+ logger = logging.getLogger("treenode.views.crud")
23
+
21
24
 
22
25
  class TreeNodeBaseAPIView(View):
23
26
  """Simple API View for TreeNode-based models."""
@@ -128,8 +131,14 @@ class TreeNodeBaseAPIView(View):
128
131
  obj.full_clean()
129
132
  obj.save()
130
133
  return JsonResponse(model_to_dict(obj), status=201)
131
- except Exception as e:
132
- return HttpResponseBadRequest(str(e))
134
+ except ValidationError as ve:
135
+ # give the client clear field errors
136
+ return JsonResponse({"errors": ve.message_dict}, status=400)
137
+ except Exception:
138
+ # log full information for development
139
+ logger.exception("Failed to create node")
140
+ return JsonResponse(
141
+ {"error": _("Failed to create node")}, status=400)
133
142
 
134
143
  def put(self, request, pk):
135
144
  """
@@ -155,10 +164,16 @@ class TreeNodeBaseAPIView(View):
155
164
  obj.full_clean()
156
165
  obj.save()
157
166
  return JsonResponse(model_to_dict(obj))
167
+ except ValidationError as ve:
168
+ return JsonResponse({"errors": ve.message_dict}, status=400)
158
169
  except self.model.DoesNotExist:
159
- return HttpResponseNotFound("Node not found.")
160
- except Exception as e:
161
- return HttpResponseBadRequest(str(e))
170
+ # give the client clear field errors
171
+ return HttpResponseNotFound(_("Node not found (pk={pk})."))
172
+ except Exception:
173
+ # log full information for development
174
+ logger.exception("Error replacing node %s", pk)
175
+ return JsonResponse(
176
+ {"error": _("Failed to update node")}, status=400)
162
177
 
163
178
  def patch(self, request, pk):
164
179
  """
@@ -200,10 +215,16 @@ class TreeNodeBaseAPIView(View):
200
215
  "id": obj.pk,
201
216
  "cascade": cascade
202
217
  })
218
+ except ValidationError as ve:
219
+ # give the client clear field errors|
220
+ return JsonResponse({"errors": ve.message_dict}, status=400)
203
221
  except self.model.DoesNotExist:
204
- return HttpResponseNotFound("Node not found.")
205
- except Exception as e:
206
- return HttpResponseBadRequest(str(e))
222
+ return HttpResponseNotFound(_("Node not found (pk={pk})."))
223
+ except Exception:
224
+ # log full information for development
225
+ logger.exception("Error deleting node %s", pk)
226
+ return JsonResponse(
227
+ {"error": _("Error deleting node")}, status=400)
207
228
 
208
229
 
209
230
  # The End
treenode/widgets.py CHANGED
@@ -21,6 +21,8 @@ from django.utils.translation import gettext_lazy as _
21
21
  class TreeWidget(forms.Widget):
22
22
  """Custom widget for hierarchical tree selection."""
23
23
 
24
+ template_name = "django/forms/widgets/select.html"
25
+
24
26
  class Media:
25
27
  """Meta class to define required CSS and JS files."""
26
28