django-fast-treenode 1.1.2__py3-none-any.whl → 2.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. {django_fast_treenode-1.1.2.dist-info → django_fast_treenode-2.0.0.dist-info}/METADATA +156 -44
  2. django_fast_treenode-2.0.0.dist-info/RECORD +41 -0
  3. {django_fast_treenode-1.1.2.dist-info → django_fast_treenode-2.0.0.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 +130 -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.2.dist-info/RECORD +0 -33
  35. treenode/compat.py +0 -8
  36. treenode/factory.py +0 -68
  37. treenode/models.py +0 -669
  38. treenode/static/select2tree/.gitkeep +0 -1
  39. treenode/static/select2tree/select2tree.css +0 -176
  40. treenode/static/select2tree/select2tree.js +0 -171
  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.2.dist-info → django_fast_treenode-2.0.0.dist-info}/LICENSE +0 -0
  49. {django_fast_treenode-1.1.2.dist-info → django_fast_treenode-2.0.0.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))