django-fast-treenode 1.1.3__py3-none-any.whl → 2.0.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {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))
|