django-fast-treenode 1.1.3__py3-none-any.whl → 2.0.1__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 (50) hide show
  1. {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/METADATA +127 -46
  2. django_fast_treenode-2.0.1.dist-info/RECORD +41 -0
  3. {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/WHEEL +1 -1
  4. treenode/__init__.py +0 -7
  5. treenode/admin.py +327 -82
  6. treenode/apps.py +20 -3
  7. treenode/cache.py +231 -0
  8. treenode/docs/Documentation +101 -54
  9. treenode/forms.py +75 -19
  10. treenode/managers.py +260 -48
  11. treenode/models/__init__.py +7 -0
  12. treenode/models/classproperty.py +24 -0
  13. treenode/models/closure.py +168 -0
  14. treenode/models/factory.py +71 -0
  15. treenode/models/proxy.py +650 -0
  16. treenode/static/treenode/css/tree_widget.css +62 -0
  17. treenode/static/treenode/css/treenode_admin.css +106 -0
  18. treenode/static/treenode/js/tree_widget.js +161 -0
  19. treenode/static/treenode/js/treenode_admin.js +171 -0
  20. treenode/templates/admin/export_success.html +26 -0
  21. treenode/templates/admin/tree_node_changelist.html +11 -0
  22. treenode/templates/admin/tree_node_export.html +27 -0
  23. treenode/templates/admin/tree_node_import.html +27 -0
  24. treenode/templates/widgets/tree_widget.css +23 -0
  25. treenode/templates/widgets/tree_widget.html +21 -0
  26. treenode/urls.py +34 -0
  27. treenode/utils/__init__.py +4 -0
  28. treenode/utils/base36.py +35 -0
  29. treenode/utils/exporter.py +141 -0
  30. treenode/utils/importer.py +296 -0
  31. treenode/version.py +11 -1
  32. treenode/views.py +102 -2
  33. treenode/widgets.py +49 -27
  34. django_fast_treenode-1.1.3.dist-info/RECORD +0 -33
  35. treenode/compat.py +0 -8
  36. treenode/factory.py +0 -68
  37. treenode/models.py +0 -668
  38. treenode/static/select2tree/.gitkeep +0 -1
  39. treenode/static/select2tree/select2tree.css +0 -176
  40. treenode/static/select2tree/select2tree.js +0 -181
  41. treenode/static/treenode/css/treenode.css +0 -85
  42. treenode/static/treenode/js/treenode.js +0 -201
  43. treenode/templates/widgets/.gitkeep +0 -1
  44. treenode/templates/widgets/attrs.html +0 -7
  45. treenode/templates/widgets/options.html +0 -1
  46. treenode/templates/widgets/select2tree.html +0 -22
  47. treenode/tests.py +0 -3
  48. {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/LICENSE +0 -0
  49. {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/top_level.txt +0 -0
  50. /treenode/{docs → templates/admin}/.gitkeep +0 -0
@@ -0,0 +1,106 @@
1
+ /*
2
+ TreeNode Admin Stylesheet
3
+
4
+ This CSS file defines styles for the TreeNode admin interface,
5
+ including tree structure visualization, interactive toggles,
6
+ and theme support.
7
+
8
+ Features:
9
+ - Tree node toggle styling for expanding/collapsing nodes.
10
+ - Supports both light and dark themes.
11
+ - Smooth hover effects and animations.
12
+ - Consistent layout adjustments for better UI interaction.
13
+
14
+ Version: 2.0.0
15
+ Author: Timur Kady
16
+ Email: timurkady@yandex.com
17
+ */
18
+
19
+
20
+ @keyframes anim {
21
+ 0% {width: 0px; height: 0px;}
22
+ 100% {width: 100px; height: 100px;}
23
+ }
24
+
25
+ .treenode-space {
26
+ display: inline-block;
27
+ width: 10px;
28
+ height: 10px;
29
+ margin-top: 0px;
30
+ margin-bottom: 0px;
31
+ margin-right: 7px !important;
32
+ background-color: transparent;
33
+ border: 1px solid transparent;
34
+ padding: 1px;
35
+ }
36
+
37
+ .treenode-toggle {
38
+ display: inline-block;
39
+ text-align: center;
40
+ font-weight: bold;
41
+ font-size: 10px;
42
+ width: 10px;
43
+ height: 10px;
44
+ line-height: 10px;
45
+ padding: 1px;
46
+ cursor: pointer;
47
+ margin-top: 0px;
48
+ margin-bottom: 0px;
49
+ margin-right: 7px !important;
50
+ border: 1px solid #ddd;
51
+ border-radius: 4px;
52
+ transition: background-color 0.2s ease, color 0.2s ease;
53
+ }
54
+
55
+ .treenode-toggle:hover {
56
+ background-color: #e0e0e0;
57
+ color: #007bff;
58
+ }
59
+
60
+ .dark-theme .treenode-toggle {
61
+ color: #ccc;
62
+ background-color: #444;
63
+ border: 1px solid #555;
64
+ }
65
+
66
+ .dark-theme .treenode-toggle:hover {
67
+ background-color: #333;
68
+ color: #fff;
69
+ }
70
+
71
+ .treenode-toggle {
72
+ font-weight: bold;
73
+ display: inline-block;
74
+
75
+ text-align: center;
76
+ cursor: pointer;
77
+ margin-right: 5px;
78
+ border-radius: 4px;
79
+
80
+ padding: 2px;
81
+ transition: background-color 0.2s ease, color 0.2s ease;
82
+ }
83
+
84
+ .treenode-toggle:hover {
85
+ background-color: #e0e0e0;
86
+ color: #007bff;
87
+ }
88
+
89
+ .dark-theme .treenode-toggle {
90
+ color: #ccc;
91
+ background-color: #444;
92
+ border: 1px solid #555;
93
+ }
94
+
95
+ .dark-theme .treenode-toggle:hover {
96
+ background-color: #333;
97
+ color: #fff;
98
+ }
99
+
100
+ .treenode-wrapper {
101
+ display: inline-block;
102
+ }
103
+
104
+ .dark-theme .treenode-toggle {
105
+ color: #ccc;
106
+ }
@@ -0,0 +1,161 @@
1
+ /*
2
+ TreeNode Select2 Widget
3
+
4
+ This script enhances the Select2 dropdown widget for hierarchical data
5
+ representation in Django admin. It supports AJAX data fetching and ensures
6
+ a structured tree-like display.
7
+
8
+ Features:
9
+ - Dynamically initializes Select2 on elements with the class `tree-widget`.
10
+ - Retrieves data via AJAX and displays it with proper indentation.
11
+ - Supports dark mode and automatically applies theme styling.
12
+ - Handles parent-child relationships and updates node priorities.
13
+
14
+ Version: 2.0.0
15
+ Author: Timur Kady
16
+ Email: timurkady@yandex.com
17
+ */
18
+
19
+
20
+ (function ($) {
21
+ "use strict";
22
+
23
+ /**
24
+ * Initializes Select2 on all elements with the class "tree-widget".
25
+ * Ensures proper AJAX data fetching and hierarchical display.
26
+ */
27
+ function initializeSelect2() {
28
+ $(".tree-widget").each(function () {
29
+ var $widget = $(this);
30
+ var url = $widget.data("url"); // Fetch the data URL for AJAX requests
31
+
32
+ if (!url) {
33
+ console.error("Error: Missing data-url for", $widget.attr("id"));
34
+ return;
35
+ }
36
+
37
+ // Initialize Select2 with AJAX support
38
+ $widget.select2({
39
+ ajax: {
40
+ url: url,
41
+ dataType: "json",
42
+ delay: 250, // Introduces a delay to avoid excessive API calls
43
+ data: function (params) {
44
+ var forwardData = $widget.data("forward") || {}; // Retrieve forwarded model data
45
+ return {
46
+ q: params.term, // Search query parameter
47
+ model: forwardData.model || null, // Pass the model name
48
+ };
49
+ },
50
+ processResults: function (data) {
51
+ if (!data.results) {
52
+ return { results: [] }; // Return an empty array if no results exist
53
+ }
54
+ return { results: data.results };
55
+ },
56
+ },
57
+ minimumInputLength: 0, // Allows opening the dropdown without typing
58
+ allowClear: true, // Enables the "clear selection" button
59
+ width: "100%", // Expands the dropdown to fit the parent container
60
+ templateResult: formatTreeResult, // Custom rendering function for hierarchical display
61
+ });
62
+
63
+ // Immediately apply theme styling after Select2 initialization
64
+ var select2Instance = $widget.data("select2");
65
+ if (select2Instance && isDarkTheme()) {
66
+ select2Instance.$container
67
+ .find(".select2-selection--single")
68
+ .addClass("dark-theme"); // Apply dark mode styling if enabled
69
+ }
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Checks whether dark mode is enabled.
75
+ * It relies on the presence of the `data-theme="dark"` attribute on the <html> tag.
76
+ * @returns {boolean} - True if dark mode is active, false otherwise.
77
+ */
78
+ function isDarkTheme() {
79
+ return document.documentElement.getAttribute("data-theme") === "dark";
80
+ }
81
+
82
+ /**
83
+ * Applies or removes the `.dark-theme` class to the Select2 dropdown and container.
84
+ * Ensures the dropdown styling follows the selected theme.
85
+ */
86
+ function applyTheme() {
87
+ var dark = isDarkTheme(); // Check if dark mode is enabled
88
+ var $dropdown = $(".select2-container--open .select2-dropdown"); // Get the currently open dropdown
89
+ var $container = $(".select2-container--open .select2-selection--single"); // Get the selection box
90
+
91
+ if (dark) {
92
+ $dropdown.addClass("dark-theme");
93
+ $container.addClass("dark-theme");
94
+ } else {
95
+ $dropdown.removeClass("dark-theme");
96
+ $container.removeClass("dark-theme");
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Formats each result in the Select2 dropdown to visually represent hierarchy.
102
+ * Adds indentation based on node depth and assigns folder/file icons.
103
+ * @param {Object} result - A single result object from the AJAX response.
104
+ * @returns {jQuery} - A formatted span element with the tree structure.
105
+ */
106
+ function formatTreeResult(result) {
107
+ if (!result.id) {
108
+ return result.text; // Return plain text for placeholder options
109
+ }
110
+ var level = result.level || 0; // Retrieve node depth (default: 0)
111
+ var is_leaf = result.is_leaf || false; // Determine if it's a leaf node
112
+ var indent = "&nbsp;&nbsp;".repeat(level); // Create indentation based on depth
113
+ var icon = is_leaf ? "📄 " : "📂 "; // Use 📄 for leaves, 📂 for parent nodes
114
+ return $("<span>" + indent + icon + result.text + "</span>"); // Return formatted text
115
+ }
116
+
117
+ /**
118
+ * Binds event listeners and initializes Select2.
119
+ * Ensures correct theme application on page load and during interactions.
120
+ */
121
+ $(document).ready(function () {
122
+ initializeSelect2();
123
+ applyTheme();
124
+
125
+ // When a Select2 dropdown opens, update its theme
126
+ $(document).on("select2:open", function () {
127
+ applyTheme();
128
+ });
129
+
130
+ // When the theme toggle button is clicked, reapply the theme
131
+ $(document).on("click", ".theme-toggle", function () {
132
+ applyTheme();
133
+ });
134
+
135
+ // When a parent changes, get the number of its children and set tn_priority
136
+ $("#id_tn_parent").on("change", function () {
137
+ var parentId = $(this).val();
138
+ var model = $(this).data("forward") ? $(this).data("forward").model : null;
139
+
140
+ if (!parentId || !model) {
141
+ console.log("No parent selected or model is missing.");
142
+ return;
143
+ }
144
+
145
+ $.ajax({
146
+ url: "/treenode/get-children-count/",
147
+ data: { parent_id: parentId, model: model },
148
+ dataType: "json",
149
+ success: function (response) {
150
+ if (response.children_count !== undefined) {
151
+ $("#id_tn_priority").val(response.children_count); // Set the value
152
+ }
153
+ },
154
+ error: function () {
155
+ console.error("Failed to fetch children count.");
156
+ }
157
+ });
158
+ });
159
+ });
160
+
161
+ })(django.jQuery || window.jQuery);
@@ -0,0 +1,171 @@
1
+ /*
2
+ TreeNode Admin JavaScript
3
+
4
+ This script enhances the Django admin interface with interactive
5
+ tree structure visualization. It provides expand/collapse functionality
6
+ for hierarchical data representation.
7
+
8
+ Features:
9
+ - Dynamically detects parent-child relationships in the admin table.
10
+ - Allows nodes to be expanded and collapsed with smooth animations.
11
+ - Saves expanded/collapsed states in localStorage for persistence.
12
+ - Optimizes tree traversal with efficient DOM manipulation.
13
+
14
+ Version: 2.0.0
15
+ Author: Timur Kady
16
+ Email: timurkady@yandex.com
17
+ */
18
+
19
+ (function($) {
20
+ $(document).ready(function() {
21
+ // Mapping parent node -> list of child nodes
22
+ var childrenMap = {};
23
+
24
+ // Iterate over all tree nodes to build a mapping of parent-child relationships
25
+ $('.treenode, .treenode-wrapper').each(function() {
26
+ var parent = $(this).data('treenode-parent'); // Get parent ID
27
+ var pk = $(this).data('treenode-pk'); // Get node primary key
28
+
29
+ if (parent) {
30
+ if (!childrenMap[parent]) {
31
+ childrenMap[parent] = [];
32
+ }
33
+ childrenMap[parent].push(pk);
34
+ }
35
+ });
36
+
37
+ // Initialize tree nodes, set up toggles
38
+ $('.treenode, .treenode-wrapper').each(function() {
39
+ var $node = $(this);
40
+ var pk = $node.data('treenode-pk'); // Get current node ID
41
+ var depth = parseInt($node.data('treenode-depth'), 10) || 0; // Get tree depth level
42
+ var $tr = $node.closest('tr'); // Find the corresponding row in the table
43
+ $tr.attr('data-treenode-depth', depth);
44
+ $tr.attr('data-treenode-pk', pk);
45
+
46
+ var $th = $tr.find('th:first');
47
+ if ($th.find('.treenode-space, .treenode-toggle').length === 0) {
48
+ var $placeholder = $('<span class="treenode-space"> </span>')
49
+ .css('margin-left', (depth * 20) + 'px'); // Indentation based on depth
50
+ $th.prepend($placeholder);
51
+ }
52
+
53
+ // If node has children, add expand/collapse toggle
54
+ if (childrenMap[pk]) {
55
+ var expanded = loadState(pk);
56
+ var $placeholder = $th.find('.treenode-space');
57
+ var $toggle = $('<span class="treenode-toggle button"></span>')
58
+ .css('margin-left', (depth * 20) + 'px');
59
+
60
+ if (expanded) {
61
+ $toggle.addClass('expanded').text('-');
62
+ } else {
63
+ $toggle.addClass('collapsed').text('+');
64
+ }
65
+ $placeholder.replaceWith($toggle);
66
+
67
+ if (!expanded) {
68
+ collapseNode($tr, true);
69
+ }
70
+
71
+ // Toggle node expansion/collapse on click
72
+ $toggle.on('click', function() {
73
+ if ($(this).hasClass('expanded')) {
74
+ collapseNode($tr, false);
75
+ $(this).removeClass('expanded').addClass('collapsed').text('+');
76
+ saveState(pk, false);
77
+ } else {
78
+ expandNode($tr);
79
+ $(this).removeClass('collapsed').addClass('expanded').text('-');
80
+ saveState(pk, true);
81
+ }
82
+ });
83
+ }
84
+ });
85
+
86
+ // Apply initial collapsed states based on saved preferences
87
+ function applyCollapsedStates() {
88
+ $('tr').each(function() {
89
+ var $tr = $(this);
90
+ var currentDepth = parseInt($tr.attr('data-treenode-depth'), 10);
91
+ var hide = false;
92
+
93
+ // Check if any ancestor is collapsed
94
+ $tr.prevAll('tr').each(function() {
95
+ var $prev = $(this);
96
+ var prevDepth = parseInt($prev.attr('data-treenode-depth'), 10);
97
+ if (prevDepth < currentDepth) {
98
+ var parentPk = $prev.attr('data-treenode-pk');
99
+ if (loadState(parentPk) === false) {
100
+ hide = true;
101
+ }
102
+ return false;
103
+ }
104
+ });
105
+
106
+ if (hide) {
107
+ $tr.hide();
108
+ }
109
+ });
110
+ }
111
+ applyCollapsedStates();
112
+
113
+ // Collapse a node and all its descendants
114
+ function collapseNode($tr, immediate) {
115
+ var parentDepth = parseInt($tr.attr('data-treenode-depth'), 10);
116
+ var $nextRows = $tr.nextAll('tr');
117
+ $nextRows.each(function() {
118
+ var $row = $(this);
119
+ var rowDepth = parseInt($row.attr('data-treenode-depth'), 10);
120
+ if (rowDepth > parentDepth) {
121
+ if (immediate) {
122
+ $row.hide();
123
+ } else {
124
+ $row.slideUp(300);
125
+ }
126
+ var childPk = $row.attr('data-treenode-pk');
127
+ saveState(childPk, false);
128
+ } else {
129
+ return false;
130
+ }
131
+ });
132
+ }
133
+
134
+ // Expand a node and reveal its immediate children
135
+ function expandNode($tr) {
136
+ var parentDepth = parseInt($tr.attr('data-treenode-depth'), 10);
137
+ var $nextRows = $tr.nextAll('tr');
138
+ $nextRows.each(function() {
139
+ var $row = $(this);
140
+ var rowDepth = parseInt($row.attr('data-treenode-depth'), 10);
141
+ if (rowDepth > parentDepth) {
142
+ if (rowDepth === parentDepth + 1) {
143
+ $row.slideDown('slow');
144
+ var childPk = $row.attr('data-treenode-pk');
145
+ if (loadState(childPk)) {
146
+ expandNode($row);
147
+ }
148
+ }
149
+ } else {
150
+ return false;
151
+ }
152
+ });
153
+ }
154
+
155
+ // Save expansion state to localStorage
156
+ function saveState(pk, isExpanded) {
157
+ if (window.localStorage) {
158
+ localStorage.setItem('treenode_state_' + pk, isExpanded ? '1' : '0');
159
+ }
160
+ }
161
+
162
+ // Load expansion state from localStorage
163
+ function loadState(pk) {
164
+ if (window.localStorage) {
165
+ var state = localStorage.getItem('treenode_state_' + pk);
166
+ return state === '1';
167
+ }
168
+ return true;
169
+ }
170
+ });
171
+ })(django.jQuery || window.jQuery);
@@ -0,0 +1,26 @@
1
+ {% extends "admin/base_site.html" %}
2
+ {% load i18n %}
3
+ {% block title %}{% trans "Export Successful" %}{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="module" style="margin-top: 20px;">
7
+ <h2>{{ message }}</h2>
8
+ <div class="module ">
9
+ <p>{{ manual_download_label }}</p>
10
+ <p><a id="dl" href="{{ download_url }}" class="button">Download</a></p>
11
+ </div>
12
+ <p>
13
+ <input type="button" class="button" value="{{ button_text }}" onclick="window.location.href='{{ redirect_url }}';">
14
+ </p>
15
+
16
+ </div>
17
+
18
+ <script type="text/javascript">
19
+ // When the page loads, create a hidden iframe to initiate the download
20
+
21
+ window.onload = function() {
22
+ document.getElementById("dl").click();
23
+ };
24
+ </script>
25
+ {% endblock %}
26
+
@@ -0,0 +1,11 @@
1
+ {% extends "admin/change_list.html" %}
2
+
3
+ {% block object-tools-items %}
4
+ {{ block.super }}
5
+ <li>
6
+ <a href="import/" class="button">Import</a>
7
+ </li>
8
+ <li>
9
+ <a href="export/" class="button">Export</a>
10
+ </li>
11
+ {% endblock %}
@@ -0,0 +1,27 @@
1
+ {% extends "admin/base_site.html" %}
2
+ {% load i18n %}
3
+ {% block content %}
4
+ <div class="module">
5
+ <h2>{% trans "Export Tree Node Data" %}</h2>
6
+ <form method="get" class="form-horizontal" style="margin-top: 20px;">
7
+ <div class="form-group">
8
+ <label for="format" class="col-sm-2 control-label">{% trans "Select format:" %}</label>
9
+ <div class="col-sm-10">
10
+ <select name="format" id="format" class="form-control">
11
+ <option value="csv">CSV</option>
12
+ <option value="json">JSON</option>
13
+ <option value="xlsx">{% trans "Excel (XLSX)" %}</option>
14
+ <option value="yaml">YAML</option>
15
+ <option value="tsv">TSV</option>
16
+ </select>
17
+ </div>
18
+ </div>
19
+ <div class="form-group" style="margin-top: 35px;">
20
+ <div class="col-sm-offset-2 col-sm-10">
21
+ <button type="submit" class="button">{% trans "Export" %}</button>
22
+ <a href=".." class="button default">{% trans "Cancel" %}</a>
23
+ </div>
24
+ </div>
25
+ </form>
26
+ </div>
27
+ {% endblock %}
@@ -0,0 +1,27 @@
1
+ {% extends "admin/base_site.html" %}
2
+ {% block content %}
3
+ <div class="module">
4
+ <h2>Import Data</h2>
5
+ {% if errors %}
6
+ <div class="errornote">
7
+ <p>Errors occurred while importing the data:</p>
8
+ <ul>
9
+ {% for error in errors %}
10
+ <li>{{ error }}</li>
11
+ {% endfor %}
12
+ </ul>
13
+ </div>
14
+ {% endif %}
15
+ <form method="post" enctype="multipart/form-data">
16
+ {% csrf_token %}
17
+ <div>
18
+ <label for="file">Choose file:</label>
19
+ <input type="file" name="file" id="file" required>
20
+ </div>
21
+ <div class="form-group" style="margin-top: 35px;">
22
+ <button type="submit" class="button">Import</button>
23
+ <a href=".." class="button cancel">Cancel</a>
24
+ </div>
25
+ </form>
26
+ </div>
27
+ {% endblock %}
@@ -0,0 +1,23 @@
1
+ /* Light mode */
2
+ .select2-container--default.select2-light .select2-selection {
3
+ background-color: #fff;
4
+ color: #333;
5
+ border: 1px solid #ccc;
6
+ }
7
+
8
+ .select2-container--default.select2-light .select2-dropdown {
9
+ background-color: #fff;
10
+ color: #333;
11
+ }
12
+
13
+ /* Dark mode */
14
+ .select2-container--default.select2-dark .select2-selection {
15
+ background-color: #222;
16
+ color: #ddd;
17
+ border: 1px solid #444;
18
+ }
19
+
20
+ .select2-container--default.select2-dark .select2-dropdown {
21
+ background-color: #222;
22
+ color: #ddd;
23
+ }
@@ -0,0 +1,21 @@
1
+ {# templates/widgets/tree_widget.html #}
2
+ {% load widget_tweaks %}
3
+ <select name="{{ widget.name }}" {% include "django/forms/widgets/attrs.html" %}>
4
+ {% if widget.optgroups %}
5
+ {% for group, options, index in widget.optgroups %}
6
+ {% if group %}
7
+ <optgroup label="{{ group }}">
8
+ {% endif %}
9
+ {% for option in options %}
10
+ <option value="{{ option.value|stringformat:'s' }}"
11
+ data-level="{{ option.attrs.data_level|default:"0" }}"
12
+ {% if option.selected %}selected{% endif %}>
13
+ {{ option.label }}
14
+ </option>
15
+ {% endfor %}
16
+ {% if group %}
17
+ </optgroup>
18
+ {% endif %}
19
+ {% endfor %}
20
+ {% endif %}
21
+ </select>
treenode/urls.py ADDED
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ TreeNode URL Configuration
4
+
5
+ This module defines URL patterns for handling AJAX requests related
6
+ to tree-structured data in Django. It includes endpoints for Select2
7
+ autocomplete and retrieving child node counts.
8
+
9
+ Routes:
10
+ - `tree-autocomplete/`: Returns JSON data for Select2 hierarchical selection.
11
+ - `get-children-count/`: Retrieves the number of children for a given
12
+ parent node.
13
+
14
+ Version: 2.0.0
15
+ Author: Timur Kady
16
+ Email: timurkady@yandex.com
17
+ """
18
+
19
+
20
+ from django.urls import path
21
+ from .views import TreeNodeAutocompleteView, GetChildrenCountView
22
+
23
+ urlpatterns = [
24
+ path(
25
+ "tree-autocomplete/",
26
+ TreeNodeAutocompleteView.as_view(),
27
+ name="tree_autocomplete"
28
+ ),
29
+ path(
30
+ "get-children-count/",
31
+ GetChildrenCountView.as_view(),
32
+ name="get_children_count"
33
+ ),
34
+ ]
@@ -0,0 +1,4 @@
1
+ from .exporter import TreeNodeExporter
2
+ from .importer import TreeNodeImporter
3
+
4
+ __all__ = ["TreeNodeExporter", "TreeNodeImporter"]
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Base36 Utility Module
4
+
5
+ This module provides a utility function for converting integers
6
+ to Base36 string representation.
7
+
8
+ Features:
9
+ - Converts integers into a more compact Base36 format.
10
+ - Maintains lexicographic order when padded with leading zeros.
11
+ - Supports negative numbers.
12
+
13
+ Version: 2.0.0
14
+ Author: Timur Kady
15
+ Email: timurkady@yandex.com
16
+ """
17
+
18
+
19
+ def to_base36(num):
20
+ """
21
+ Convert an integer to a base36 string.
22
+
23
+ For example: 10 -> 'A', 35 -> 'Z', 36 -> '10', etc.
24
+ """
25
+ digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
26
+
27
+ if num == 0:
28
+ return '0'
29
+ sign = '-' if num < 0 else ''
30
+ num = abs(num)
31
+ result = []
32
+ while num:
33
+ num, rem = divmod(num, 36)
34
+ result.append(digits[rem])
35
+ return sign + ''.join(reversed(result))