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.
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/METADATA +127 -46
- django_fast_treenode-2.0.1.dist-info/RECORD +41 -0
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/WHEEL +1 -1
- treenode/__init__.py +0 -7
- treenode/admin.py +327 -82
- treenode/apps.py +20 -3
- treenode/cache.py +231 -0
- treenode/docs/Documentation +101 -54
- treenode/forms.py +75 -19
- treenode/managers.py +260 -48
- treenode/models/__init__.py +7 -0
- treenode/models/classproperty.py +24 -0
- treenode/models/closure.py +168 -0
- treenode/models/factory.py +71 -0
- treenode/models/proxy.py +650 -0
- treenode/static/treenode/css/tree_widget.css +62 -0
- treenode/static/treenode/css/treenode_admin.css +106 -0
- treenode/static/treenode/js/tree_widget.js +161 -0
- treenode/static/treenode/js/treenode_admin.js +171 -0
- treenode/templates/admin/export_success.html +26 -0
- treenode/templates/admin/tree_node_changelist.html +11 -0
- treenode/templates/admin/tree_node_export.html +27 -0
- treenode/templates/admin/tree_node_import.html +27 -0
- treenode/templates/widgets/tree_widget.css +23 -0
- treenode/templates/widgets/tree_widget.html +21 -0
- treenode/urls.py +34 -0
- treenode/utils/__init__.py +4 -0
- treenode/utils/base36.py +35 -0
- treenode/utils/exporter.py +141 -0
- treenode/utils/importer.py +296 -0
- treenode/version.py +11 -1
- treenode/views.py +102 -2
- treenode/widgets.py +49 -27
- django_fast_treenode-1.1.3.dist-info/RECORD +0 -33
- treenode/compat.py +0 -8
- treenode/factory.py +0 -68
- treenode/models.py +0 -668
- treenode/static/select2tree/.gitkeep +0 -1
- treenode/static/select2tree/select2tree.css +0 -176
- treenode/static/select2tree/select2tree.js +0 -181
- treenode/static/treenode/css/treenode.css +0 -85
- treenode/static/treenode/js/treenode.js +0 -201
- treenode/templates/widgets/.gitkeep +0 -1
- treenode/templates/widgets/attrs.html +0 -7
- treenode/templates/widgets/options.html +0 -1
- treenode/templates/widgets/select2tree.html +0 -22
- treenode/tests.py +0 -3
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/LICENSE +0 -0
- {django_fast_treenode-1.1.3.dist-info → django_fast_treenode-2.0.1.dist-info}/top_level.txt +0 -0
- /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 = " ".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,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
|
+
]
|
treenode/utils/base36.py
ADDED
@@ -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))
|