django-fast-treenode 3.0.6__py3-none-any.whl → 3.0.8__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 (37) hide show
  1. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/METADATA +1 -1
  2. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/RECORD +37 -37
  3. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/WHEEL +1 -1
  4. treenode/__init__.py +1 -0
  5. treenode/admin/admin.py +105 -109
  6. treenode/admin/mixin.py +1 -1
  7. treenode/managers/queries.py +18 -6
  8. treenode/managers/tasks.py +1 -1
  9. treenode/models/models.py +2 -2
  10. treenode/static/css/treenode_admin.css +21 -0
  11. treenode/static/js/treenode_admin.js +130 -339
  12. treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -384
  13. treenode/static/vendors/jquery-ui/LICENSE.txt +43 -43
  14. treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -10716
  15. treenode/static/vendors/jquery-ui/index.html +297 -297
  16. treenode/static/vendors/jquery-ui/jquery-ui.css +438 -438
  17. treenode/static/vendors/jquery-ui/jquery-ui.js +5222 -5222
  18. treenode/static/vendors/jquery-ui/jquery-ui.min.css +6 -6
  19. treenode/static/vendors/jquery-ui/jquery-ui.min.js +5 -5
  20. treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -16
  21. treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +4 -4
  22. treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -439
  23. treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +4 -4
  24. treenode/static/vendors/jquery-ui/package.json +82 -82
  25. treenode/templates/treenode/admin/treenode_changelist.html +61 -15
  26. treenode/templates/treenode/admin/treenode_rows.html +37 -40
  27. treenode/templatetags/treenode_admin.py +57 -14
  28. treenode/utils/db/sqlcompat.py +1 -33
  29. treenode/utils/db/sqlquery.py +0 -24
  30. treenode/version.py +2 -2
  31. treenode/views/autoapi.py +91 -91
  32. treenode/views/autocomplete.py +52 -52
  33. treenode/views/children.py +41 -41
  34. treenode/views/common.py +23 -23
  35. treenode/widgets.py +0 -2
  36. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/licenses/LICENSE +0 -0
  37. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.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.6
3
+ Version: 3.0.8
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
@@ -1,5 +1,5 @@
1
- django_fast_treenode-3.0.6.dist-info/licenses/LICENSE,sha256=SSYqS84FCnAW7tAxmjBKU8qAa8Jv4VGPuSSGeHwWtJE,1095
2
- treenode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1
+ django_fast_treenode-3.0.8.dist-info/licenses/LICENSE,sha256=SSYqS84FCnAW7tAxmjBKU8qAa8Jv4VGPuSSGeHwWtJE,1095
2
+ treenode/__init__.py,sha256=3z1hWpHyy4wg6uz7HCmRi9FaXYeN5CfANVpa77UIoPw,53
3
3
  treenode/apps.py,sha256=QlwjNDM9rkUoWB8Vm8-OkS6lNx0-aTByuGZlu9wrQMs,1832
4
4
  treenode/cache.py,sha256=2jUiiecfFxwB7QFukpU4u0FnDzGH6hNRfo6KAYvs6vM,8447
5
5
  treenode/forms.py,sha256=V-upmbYSW1BbuXdSBGExHxw_j5TTUheaHvreK9tSGTE,3155
@@ -7,22 +7,22 @@ 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=AVU54ETwjuKnFIx-aNjKEDEPQRV3Odz7S2YywkIjue8,220
11
- treenode/widgets.py,sha256=61ed16bVqb1_R97jekDrbKS5pDVK4nzXSDwL3CDBYEk,4075
10
+ treenode/version.py,sha256=5TH3IchoS9D8YEFnTIlRrjoALxMdemh1PCN0Bb9Jnss,220
11
+ treenode/widgets.py,sha256=VItPvN9XgaSRI_MZjKEmtaHDJcn2bDIQIppwKjXmYQM,4017
12
12
  treenode/admin/__init__.py,sha256=XNEYHdF5lKb0vpdlVxdR2fxj5oUgzyx1YyCwsv0gxHw,100
13
- treenode/admin/admin.py,sha256=UvO8EuABgXMG3EjkNjPhlL1yWp55JIwN85iOr8pHZ9I,7758
13
+ treenode/admin/admin.py,sha256=uORuVSx-p0MYXLRVl54cBa7IAL9zicyeYTX3Cv5pBDc,7462
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=ssOfk6xJ8BculZrVRFUoHpNOr4jK9CUkcKnqzqyd5kM,10648
17
+ treenode/admin/mixin.py,sha256=jOkKKt0CpknGokluW8T1YB7VhkCkaAAMYBuptbYDXAw,10648
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=zZYeDVFXl-Hro9ubv0_zuN7XaVXQhyeeFx_GVZSMi30,10273
21
- treenode/managers/tasks.py,sha256=b8deUAbCpD1Yov-PpjKNAx49rL4fejkGf64ih5JziF0,7138
20
+ treenode/managers/queries.py,sha256=Kepax8SDn7G5tOlPRWBCp5Oyp49O5iMITCMBoNCm_Ak,10655
21
+ treenode/managers/tasks.py,sha256=nVlGDxNnFlZS1-oU8UH_yTXJERcGrAZ9kCmKwKO5WuY,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
25
- treenode/models/models.py,sha256=4S4SFFSxuIlY1DjuGW2T_bJaOos9chp1cuJ5k4_4gPg,12279
25
+ treenode/models/models.py,sha256=n_2nCJzBOEcmh3r-GCoHV3ZiTX8Xe1ewK9SPptvI_M0,12273
26
26
  treenode/models/mixins/__init__.py,sha256=aALVKMGAWbgMAeKWS6s-NF3L5FmRX96mQxtpthOX-Ec,805
27
27
  treenode/models/mixins/ancestors.py,sha256=9g-0nPHoiF_SX2kN4uDLdbWyw-TDCz1YqxLJngwTZOQ,1971
28
28
  treenode/models/mixins/children.py,sha256=H9iMqgucSmwLX-3O3QUj1a2PUQTmmWZ4GPPjRZ9a5E4,2399
@@ -38,25 +38,25 @@ treenode/models/mixins/update.py,sha256=oCZkMnfT23n2n3_mnokDrtTLS_jO_lJRwG3ENkH_
38
38
  treenode/static/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  treenode/static/css/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
40
40
  treenode/static/css/tree_widget.css,sha256=YVIHuZT_E5ck5N7wvYvuwgV_m-4Ta_uGFiNUoTw-zbk,4861
41
- treenode/static/css/treenode_admin.css,sha256=_AVEO52atudOVBspgH4ecubbgGUeKhcC13WSxo7QQAw,3021
41
+ treenode/static/css/treenode_admin.css,sha256=w7yyyf4aFsJKlr_YJPVq6ap5ZvA5Co7ZCYq6ipuV958,3291
42
42
  treenode/static/css/treenode_tabs.css,sha256=pbulaUEhNMQFNWXFQrRN1zMyKjoSqLAnjNygpouPUfE,879
43
43
  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
- treenode/static/js/treenode_admin.js,sha256=fkxAj50QxwpPVdQxDNxQZ5vR0goUt53h8FzOv_fjxBQ,15945
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
46
+ treenode/static/js/treenode_admin.js,sha256=IU3Zj2Q-WBxftl0zsjeRPrt1xOzzdEKlzGEMThi3hrk,9706
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
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
@@ -67,27 +67,27 @@ treenode/templates/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
67
67
  treenode/templates/treenode/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
68
68
  treenode/templates/treenode/admin/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
69
69
  treenode/templates/treenode/admin/treenode_ajax_rows.html,sha256=zFyPaTbSyxRjOqQ85SMv__qTIYDjEna6chYODBypDZA,224
70
- treenode/templates/treenode/admin/treenode_changelist.html,sha256=ALU8iZLCOIiiGIf5RTdv3Bb694pG_YyQ-wcbz1CkBKc,654
70
+ treenode/templates/treenode/admin/treenode_changelist.html,sha256=JiUwX33w5WwegX7gM2ctw-bx9XOyXwKt9QB_6g7Dfz8,2283
71
71
  treenode/templates/treenode/admin/treenode_import_export.html,sha256=K6L_JnhGpWgQdduXSfAJ0UuTBfSN4ylFw9w_e5tPDDE,2926
72
- treenode/templates/treenode/admin/treenode_rows.html,sha256=S1XtZXWMUdfgjDoKG7OJwZg81a_5IKPp7tyrrHhJbBk,1695
72
+ treenode/templates/treenode/admin/treenode_rows.html,sha256=de90wKpNdVB2C5CGnIkStBB0dWCDkmWr_pluV_NgBUs,1857
73
73
  treenode/templates/treenode/widgets/tree_widget.html,sha256=GKcCU-B2FkkJ2BSOuXOw9e_PdYTtADcvyITEXqOlZ9Y,723
74
74
  treenode/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
- treenode/templatetags/treenode_admin.py,sha256=0Xz5tS_Etf2FU69Ww7GfQ6u2_jh97jymD1Gc4XCpHbc,1204
75
+ treenode/templatetags/treenode_admin.py,sha256=5f5oqAS4zC_f0kkJRsm5MqXHjsKJXn8GslbZcbMivxg,2736
76
76
  treenode/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  treenode/utils/db/__init__.py,sha256=RwicAcJSI1nhIPWLdT7j9TFsgOc9834VDn9lVn54GlY,247
78
78
  treenode/utils/db/compiler.py,sha256=PgD9ybS5H8OUHw1gkFBQHhnrf5HiCx8QXUMRhydwh7o,3824
79
79
  treenode/utils/db/db_vendor.py,sha256=4SyEHl51jVCDB3is4omHCf2bTB_QV3RvemUQYxJP5m0,930
80
80
  treenode/utils/db/service.py,sha256=PF85Yhz2xUWFFCzpLYotmiNTZXXEH61rhswslSxEUds,2640
81
- treenode/utils/db/sqlcompat.py,sha256=FTgEMu3kQ1c96nB1sUW-SFOmYEvdl8JlD9HvsRj_dO0,5704
82
- treenode/utils/db/sqlquery.py,sha256=jHI0gLkdJCD6ulOenO4b0aSjyAxtJ4ZGvsQa0i3Aj8E,2958
81
+ treenode/utils/db/sqlcompat.py,sha256=K71ggkKIvpdTtHQ6Y4qcbo6cj2eYiEfy6DlVBr8Po1E,4460
82
+ treenode/utils/db/sqlquery.py,sha256=KXcfKbbaBF-D134H_2DiPQtjedR79SJNXPJc0msZYEc,1938
83
83
  treenode/views/__init__.py,sha256=ppxbBx51TUaKstJFpAd_DTmbKjbZGmVMLNYSpgUKnd0,111
84
- treenode/views/autoapi.py,sha256=o75e8IFsogbhZN_rbx3BKVnoruD96nWelnC5UzOqUDw,3628
85
- treenode/views/autocomplete.py,sha256=Z7cBnC4Ihdyxm8zlbnG6CkZdVkM3TOTWRpw5mdhaIVA,1469
86
- treenode/views/children.py,sha256=bygXaEBExxG3zIPL34_PYHLFFIqlQU2naqPIlyQ6e-s,1152
87
- treenode/views/common.py,sha256=mrmr40R91XVbMWcz5GZT-OjpnQ87F7XQZxu1W6rqpqI,617
84
+ treenode/views/autoapi.py,sha256=IAQfhWey4z-QBuAn-QDHhpJSFCF702sHtqYOA6vK4BY,3537
85
+ treenode/views/autocomplete.py,sha256=ERGJT4jfCE2GUv94Ja8SgJyG1FOAyqIeB0PKH1fyU7g,1417
86
+ treenode/views/children.py,sha256=seO0SNKriRY2FJOO1oZgX42iKolXMf3EF76GrfdJZLQ,1111
87
+ treenode/views/common.py,sha256=kUN3IgMZCdNdJ2haxB9MTGcn2rctyFUAHNagzcu9wXk,594
88
88
  treenode/views/crud.py,sha256=RI5rdyD4hZTszjZFThByxi_lkAeJlqbDCXFkD8iyzKE,7424
89
89
  treenode/views/search.py,sha256=c_GyooT3jyoNa96bBxfoWruRN1wIw-ZGYvwGKkGojTs,1501
90
- django_fast_treenode-3.0.6.dist-info/METADATA,sha256=gWtZYZEHkn5pnEw54QUVz3B_CORzh7HsXx0eQR0sl9Y,10249
91
- django_fast_treenode-3.0.6.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
92
- django_fast_treenode-3.0.6.dist-info/top_level.txt,sha256=fmgxHbXyx1O2MPi_9kjx8aL9L-8TmV0gre4Go8XgqFk,9
93
- django_fast_treenode-3.0.6.dist-info/RECORD,,
90
+ django_fast_treenode-3.0.8.dist-info/METADATA,sha256=-DOMkpuNAIruYNiRcCc7g6zGVpbWjter7IEDb9k10mg,10249
91
+ django_fast_treenode-3.0.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
92
+ django_fast_treenode-3.0.8.dist-info/top_level.txt,sha256=fmgxHbXyx1O2MPi_9kjx8aL9L-8TmV0gre4Go8XgqFk,9
93
+ django_fast_treenode-3.0.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
treenode/__init__.py CHANGED
@@ -0,0 +1 @@
1
+ default_app_config = 'treenode.apps.TreeNodeConfig'
treenode/admin/admin.py CHANGED
@@ -2,7 +2,26 @@
2
2
  """
3
3
  TreeNode Admin Model Class
4
4
 
5
- Version: 3.0.0
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
6
25
  Author: Timur Kady
7
26
  Email: timurkady@yandex.com
8
27
  """
@@ -10,12 +29,11 @@ Email: timurkady@yandex.com
10
29
 
11
30
  from django.contrib import admin
12
31
  from django.db import models
13
- from django.http import HttpResponseRedirect
14
32
  from django.urls import reverse
33
+ from django.utils.html import escape
15
34
  from django.utils.safestring import mark_safe
16
35
  from django.utils.translation import gettext_lazy as _
17
36
 
18
- from .changelist import TreeNodeChangeList
19
37
  from .mixin import AdminMixin
20
38
  from ..forms import TreeNodeForm
21
39
  from ..widgets import TreeWidget
@@ -33,14 +51,12 @@ class TreeNodeModelAdmin(AdminMixin, admin.ModelAdmin):
33
51
  # Режимы отображения
34
52
  TREENODE_DISPLAY_MODE_ACCORDION = 'accordion'
35
53
  TREENODE_DISPLAY_MODE_BREADCRUMBS = 'breadcrumbs'
36
- TREENODE_DISPLAY_MODE_INDENTATION = 'indentation'
37
54
  treenode_display_mode = TREENODE_DISPLAY_MODE_ACCORDION
38
55
 
39
56
  form = TreeNodeForm
40
57
  importer_class = None
41
58
  exporter_class = None
42
59
  ordering = []
43
- list_per_page = 1000
44
60
 
45
61
  formfield_overrides = {
46
62
  models.ForeignKey: {'widget': TreeWidget()},
@@ -88,79 +104,69 @@ class TreeNodeModelAdmin(AdminMixin, admin.ModelAdmin):
88
104
 
89
105
  toggle.short_description = _("Expand")
90
106
 
91
- def _get_treenode_field_display(self, request, obj):
92
- """Return HTML for the tree node in list view."""
93
- level = obj.get_depth()
94
- edit_url = reverse(
95
- f"admin:{obj._meta.app_label}_{obj._meta.model_name}_change",
96
- args=[obj.pk]
97
- )
107
+ def get_changelist(self, request, **kwargs):
108
+ """Get changelist."""
109
+ ChangeList = super().get_changelist(request, **kwargs)
98
110
 
99
- if self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_ACCORDION:
100
- icon = "📄 " if obj.is_leaf() else "📁 "
101
- content = (
102
- f'<span style="padding-left: {level * 1.5}em;">'
103
- f'{icon}<a href="{edit_url}">{str(obj)}</a></span>'
104
- )
105
- elif self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_BREADCRUMBS: # noqa
106
- breadcrumbs = obj.get_breadcrumbs(
107
- attr=getattr(obj, 'treenode_display_field', 'id'))
108
- content = " / ".join(map(str, breadcrumbs))
109
- elif self.treenode_display_mode == self.TREENODE_DISPLAY_MODE_INDENTATION: # noqa
110
- indent = "&mdash;" * level
111
- content = f'{indent}<a href="{edit_url}">{str(obj)}</a>'
112
- else:
113
- content = f'<a href="{edit_url}">{str(obj)}</a>'
114
-
115
- html = (
116
- f'<div class="treenode-wrapper" '
117
- f'data-treenode-pk="{obj.pk}" '
118
- f'data-treenode-depth="{level}" '
119
- f'data-treenode-parent="{obj.parent_id or ""}">'
120
- f'<span class="treenode-content">{content}</span>'
121
- f'</div>'
122
- )
123
- return mark_safe(html)
111
+ class NoPaginationChangeList(ChangeList):
112
+ """Suppress pagination."""
124
113
 
125
- def get_list_display(self, request):
126
- """Generate list_display dynamically with tree-aware columns."""
127
- # Define callable that replaces display field with tree field
128
- def treenode_field(obj):
129
- return self._get_treenode_field_display(request, obj)
130
- treenode_field.short_description = self.model._meta.verbose_name
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)
131
123
 
132
- display_field = getattr(self.model, 'display_field', '__str__')
124
+ return NoPaginationChangeList
133
125
 
134
- user_list_display = list(super().get_list_display(request))
135
- # If the list is empty or only contains __str__, replace it entirely
136
- if not user_list_display or user_list_display == ['__str__']:
137
- user_list_display = [treenode_field]
138
- else:
139
- try:
140
- pos = user_list_display.index(display_field)
141
- user_list_display.pop(pos)
142
- user_list_display.insert(pos, treenode_field)
143
- except ValueError:
144
- user_list_display.insert(0, treenode_field)
126
+ def get_changelist_instance(self, request):
127
+ """
128
+ Get changelist instance.
145
129
 
146
- return (self.drag, self.toggle) + tuple(user_list_display)
130
+ Make sure our custom ChangeList is used without pagination.
131
+ """
132
+ ChangeList = self.get_changelist(request)
147
133
 
148
- def get_list_display_links(self, request, list_display):
149
- """Get display list links."""
150
- return ('treenode_field',)
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
+ )
151
153
 
152
154
  def get_queryset(self, request):
153
- """By default: only root nodes, unless searching or editing."""
155
+ """Get queryset."""
154
156
  qs = super().get_queryset(request)
157
+ return qs.select_related('parent')\
158
+ .prefetch_related('children')\
159
+ .order_by('_path')
155
160
 
156
- if request.GET.get("q"):
157
- return qs
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)
158
165
 
159
- resolved = request.resolver_match
160
- if resolved and resolved.url_name.endswith("_change"):
161
- return qs
166
+ description = str(self.model._meta.verbose_name)
167
+ treenode_field.short_description = description
162
168
 
163
- return qs.filter(parent__isnull=True)
169
+ return (self.drag, self.toggle, treenode_field)
164
170
 
165
171
  def get_form(self, request, obj=None, **kwargs):
166
172
  """Get Form method."""
@@ -171,56 +177,46 @@ class TreeNodeModelAdmin(AdminMixin, admin.ModelAdmin):
171
177
 
172
178
  def get_search_fields(self, request):
173
179
  """Get search fields."""
174
- return [getattr(self.model, 'treenode_display_field', 'id')]
175
-
176
- def get_changelist(self, request, **kwargs):
177
- """Get ChangeList Class."""
178
- return TreeNodeChangeList
179
-
180
- def get_ordering(self, request):
181
- """Get ordering."""
182
- return None
183
-
184
- def changelist_view(self, request, extra_context=None):
185
- """Changelist View."""
186
- extra_context = extra_context or {}
187
- # TODO
188
- extra_context['import_export_enabled'] = self.import_export
189
- extra_context['num_sorted_fields'] = len(self.get_ordering(request) or []) # noqa: D501
190
-
191
- response = super().changelist_view(request, extra_context=extra_context)
192
-
193
- # If response is a redirect, then there is no point in updating
194
- # ChangeList
195
- if isinstance(response, HttpResponseRedirect):
196
- return response
180
+ return [getattr(self.model, 'display_field', 'id') or 'id']
197
181
 
198
- ChangeListClass = self.get_changelist(request)
199
- cl = ChangeListClass(
200
- request,
201
- self.model,
202
- self.list_display,
203
- self.list_display_links,
204
- self.list_filter,
205
- self.date_hierarchy,
206
- self.search_fields,
207
- self.list_select_related,
208
- self.list_per_page,
209
- self.list_max_show_all,
210
- self.list_editable,
211
- self,
212
- self.get_sortable_by(request),
213
- self.get_search_help_text(request),
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]
214
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()]
215
212
 
216
- cl.get_results(request)
217
- cl.result_list = self.render_changelist_rows(cl.result_list, request)
213
+ text = "/" + "/".join([escape(label) for label in breadcrumbs])
218
214
 
219
- return response
215
+ content = f'{padding}{icon}<a href="{edit_url}">{escape(text)}</a>{closing}' # noqa
216
+ return mark_safe(content)
220
217
 
221
218
  def get_list_per_page(self, request):
222
219
  """Get list per page."""
223
220
  return 999999
224
221
 
225
-
226
222
  # The End
treenode/admin/mixin.py CHANGED
@@ -4,7 +4,7 @@ TreeNode Admin Model Class Mixin
4
4
 
5
5
  Views for AdminModel
6
6
 
7
- Version: 3.0.0
7
+ Version: 3.0.7
8
8
  Author: Timur Kady
9
9
  Email: timurkady@yandex.com
10
10
  """
@@ -5,14 +5,13 @@ 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.4
8
+ Version: 3.0.0
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
16
15
 
17
16
 
18
17
  class TreeQuery:
@@ -33,6 +32,19 @@ class TreeQuery:
33
32
  cursor.execute(sql, params)
34
33
  return cursor.fetchall()
35
34
 
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
+
36
48
  def order_by(self, sql, order_by_clause):
37
49
  """Wrap the SQL in an outer query to enforce ordering."""
38
50
  return f"SELECT * FROM ({sql}) AS combined ORDER BY {order_by_clause}"
@@ -64,7 +76,7 @@ class TreeQuery:
64
76
  if include_self:
65
77
  sql2 = f"SELECT id, priority FROM {self.db_table} WHERE id = %s"
66
78
  params2 = [self.node.pk]
67
- combined_sql, combined_params = SQLCompat.wrap_union_all(
79
+ combined_sql, combined_params = self.wrap_union_all(
68
80
  [(sql1, params1), (sql2, params2)])
69
81
  sql = self.order_by(combined_sql, "priority")
70
82
  return sql, combined_params
@@ -103,7 +115,7 @@ class TreeQuery:
103
115
  FROM {self.db_table}
104
116
  WHERE id = %s
105
117
  """
106
- union_sql, union_params = SQLCompat.wrap_union_all([
118
+ union_sql, union_params = self.wrap_union_all([
107
119
  (base_sql, params),
108
120
  (sql_self, [self.node.pk])
109
121
  ])
@@ -136,7 +148,7 @@ class TreeQuery:
136
148
 
137
149
  if include_self:
138
150
  sql_self = f"SELECT id, _depth, priority FROM {self.db_table} WHERE id = %s" # noqa: D501
139
- union_sql, union_params = SQLCompat.wrap_union_all(
151
+ union_sql, union_params = self.wrap_union_all(
140
152
  [(base_sql, params), (sql_self, [self.node.pk])])
141
153
  else:
142
154
  union_sql, union_params = base_sql, params
@@ -186,7 +198,7 @@ class TreeQuery:
186
198
  if include_self:
187
199
  sql_self = f"SELECT id, _depth, priority FROM {self.db_table} WHERE id = %s" # noqa: D501
188
200
  queries.append((sql_self, [self.node.pk]))
189
- combined_sql, combined_params = SQLCompat.wrap_union_all(queries)
201
+ combined_sql, combined_params = self.wrap_union_all(queries)
190
202
  combined_sql = self.order_by(combined_sql, "_depth, priority")
191
203
  return combined_sql, combined_params
192
204
 
@@ -2,7 +2,7 @@
2
2
  """
3
3
  TreeNode TaskQuery manager
4
4
 
5
- Version: 3.0.4
5
+ Version: 3.0.1
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
treenode/models/models.py CHANGED
@@ -14,7 +14,7 @@ Features:
14
14
  - Provides a caching mechanism to optimize performance.
15
15
  - Includes methods for tree traversal, manipulation, and serialization.
16
16
 
17
- Version: 3.0.0
17
+ Version: 3.0.7
18
18
  Author: Timur Kady
19
19
  Email: timurkady@yandex.com
20
20
 
@@ -122,7 +122,7 @@ class TreeNodeModel(
122
122
 
123
123
  def __str__(self):
124
124
  """Return a human-readable string representation of an object."""
125
- field = getattr(type(self), 'display_field', None)
125
+ field = getattr(self, 'display_field', None)
126
126
  if field and hasattr(self, field):
127
127
  return str(getattr(self, field))
128
128
  return f'Node {self.pk}'
@@ -66,6 +66,27 @@ Email: timurkady@yandex.com
66
66
  opacity: 1.0;
67
67
  }
68
68
 
69
+ .treenode-toolbar{
70
+ display: flex;
71
+ }
72
+
73
+ .treenode-toolbar {
74
+ margin: 15px 0px;
75
+ }
76
+
77
+ .treenode-button {
78
+ padding: 5px !important;
79
+ margin-left: 15px !important;
80
+ }
81
+
82
+ tr.treenode-hidden {
83
+ display: none;
84
+ }
85
+
86
+ td.action-checkbox{
87
+ text-align: center;
88
+ }
89
+
69
90
  .dark-theme .treenode-toggle {
70
91
  color: #ccc;
71
92
  background-color: #444;