django-fast-treenode 3.0.6__py3-none-any.whl → 3.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/METADATA +1 -1
- {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/RECORD +37 -37
- {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/WHEEL +1 -1
- treenode/__init__.py +1 -0
- treenode/admin/admin.py +105 -109
- treenode/admin/mixin.py +1 -1
- treenode/managers/queries.py +18 -6
- treenode/managers/tasks.py +1 -1
- treenode/models/models.py +2 -2
- treenode/static/css/treenode_admin.css +21 -0
- treenode/static/js/treenode_admin.js +130 -339
- treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -384
- treenode/static/vendors/jquery-ui/LICENSE.txt +43 -43
- treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -10716
- treenode/static/vendors/jquery-ui/index.html +297 -297
- treenode/static/vendors/jquery-ui/jquery-ui.css +438 -438
- treenode/static/vendors/jquery-ui/jquery-ui.js +5222 -5222
- treenode/static/vendors/jquery-ui/jquery-ui.min.css +6 -6
- treenode/static/vendors/jquery-ui/jquery-ui.min.js +5 -5
- treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -16
- treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +4 -4
- treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -439
- treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +4 -4
- treenode/static/vendors/jquery-ui/package.json +82 -82
- treenode/templates/treenode/admin/treenode_changelist.html +61 -15
- treenode/templates/treenode/admin/treenode_rows.html +37 -40
- treenode/templatetags/treenode_admin.py +57 -14
- treenode/utils/db/sqlcompat.py +1 -33
- treenode/utils/db/sqlquery.py +0 -24
- treenode/version.py +2 -2
- treenode/views/autoapi.py +91 -91
- treenode/views/autocomplete.py +52 -52
- treenode/views/children.py +41 -41
- treenode/views/common.py +23 -23
- treenode/widgets.py +0 -2
- {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/licenses/LICENSE +0 -0
- {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/top_level.txt +0 -0
treenode/views/autoapi.py
CHANGED
@@ -1,91 +1,91 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
Route generator for all models inherited from TreeNodeModel
|
4
|
-
|
5
|
-
Version: 3.0.0
|
6
|
-
Author: Timur Kady
|
7
|
-
Email: timurkady@yandex.com
|
8
|
-
"""
|
9
|
-
|
10
|
-
|
11
|
-
from django.apps import apps
|
12
|
-
from django.urls import path
|
13
|
-
from django.conf import settings
|
14
|
-
from django.contrib.auth.decorators import login_required
|
15
|
-
|
16
|
-
from ..models import TreeNodeModel
|
17
|
-
from .autocomplete import TreeNodeAutocompleteView
|
18
|
-
from .children import TreeChildrenView
|
19
|
-
from .search import TreeSearchView
|
20
|
-
from .crud import TreeNodeBaseAPIView
|
21
|
-
|
22
|
-
|
23
|
-
class AutoTreeAPI:
|
24
|
-
"""Auto-discover and expose TreeNode-based APIs."""
|
25
|
-
|
26
|
-
def __init__(self, base_view=TreeNodeBaseAPIView, base_url="api"):
|
27
|
-
"""Init auto-discover."""
|
28
|
-
self.base_view = base_view
|
29
|
-
self.base_url = base_url
|
30
|
-
|
31
|
-
def protect_view(self, view, model):
|
32
|
-
"""
|
33
|
-
Protect view.
|
34
|
-
|
35
|
-
Protects view with login_required if needed, based on model attribute
|
36
|
-
or global settings.
|
37
|
-
"""
|
38
|
-
if getattr(model, 'api_login_required', None) is True:
|
39
|
-
return login_required(view)
|
40
|
-
if getattr(settings, 'TREENODE_API_LOGIN_REQUIRED', False):
|
41
|
-
return login_required(view)
|
42
|
-
return view
|
43
|
-
|
44
|
-
def discover(self):
|
45
|
-
"""Scan models and generate API urls."""
|
46
|
-
urls = [
|
47
|
-
# Admin and Widget end-points
|
48
|
-
path("widget/autocomplete/", TreeNodeAutocompleteView.as_view(), name="tree_autocomplete"), # noqa: D501
|
49
|
-
path("widget/children/", TreeChildrenView.as_view(), name="tree_children"), # noqa: D501
|
50
|
-
path("widget/search/", TreeSearchView.as_view(), name="tree_search"), # noqa: D501
|
51
|
-
]
|
52
|
-
for model in apps.get_models():
|
53
|
-
if issubclass(model, TreeNodeModel) and model is not TreeNodeModel:
|
54
|
-
model_name = model._meta.model_name
|
55
|
-
|
56
|
-
# Dynamically create an API view class for the model
|
57
|
-
api_view_class = type(
|
58
|
-
f"{model_name.capitalize()}APIView",
|
59
|
-
(self.base_view,),
|
60
|
-
{"model": model}
|
61
|
-
)
|
62
|
-
|
63
|
-
# List of API actions and their corresponding URL patterns
|
64
|
-
action_patterns = [
|
65
|
-
# List / Create
|
66
|
-
("", None, f"{model_name}-list"),
|
67
|
-
# Retrieve / Update / Delete
|
68
|
-
("<int:pk>/", None, f"{model_name}-detail"),
|
69
|
-
("tree/", {'action': 'tree'}, f"{model_name}-tree"),
|
70
|
-
# Direct children
|
71
|
-
("<int:pk>/children/", {'action': 'children'}, f"{model_name}-children"), # noqa: D501
|
72
|
-
# All descendants
|
73
|
-
("<int:pk>/descendants/", {'action': 'descendants'}, f"{model_name}-descendants"), # noqa: D501
|
74
|
-
# Ancestors + Self + Descendants
|
75
|
-
("<int:pk>/family/", {'action': 'family'}, f"{model_name}-family"), # noqa: D501
|
76
|
-
]
|
77
|
-
|
78
|
-
# Create secured view instance once
|
79
|
-
view = self.protect_view(api_view_class.as_view(), model)
|
80
|
-
|
81
|
-
# Automatically build all paths for this model
|
82
|
-
for url_suffix, extra_kwargs, route_name in action_patterns:
|
83
|
-
urls.append(
|
84
|
-
path(
|
85
|
-
f"{self.base_url}/{model_name}/{url_suffix}",
|
86
|
-
view,
|
87
|
-
extra_kwargs or {},
|
88
|
-
name=route_name
|
89
|
-
)
|
90
|
-
)
|
91
|
-
return urls
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
Route generator for all models inherited from TreeNodeModel
|
4
|
+
|
5
|
+
Version: 3.0.0
|
6
|
+
Author: Timur Kady
|
7
|
+
Email: timurkady@yandex.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
|
11
|
+
from django.apps import apps
|
12
|
+
from django.urls import path
|
13
|
+
from django.conf import settings
|
14
|
+
from django.contrib.auth.decorators import login_required
|
15
|
+
|
16
|
+
from ..models import TreeNodeModel
|
17
|
+
from .autocomplete import TreeNodeAutocompleteView
|
18
|
+
from .children import TreeChildrenView
|
19
|
+
from .search import TreeSearchView
|
20
|
+
from .crud import TreeNodeBaseAPIView
|
21
|
+
|
22
|
+
|
23
|
+
class AutoTreeAPI:
|
24
|
+
"""Auto-discover and expose TreeNode-based APIs."""
|
25
|
+
|
26
|
+
def __init__(self, base_view=TreeNodeBaseAPIView, base_url="api"):
|
27
|
+
"""Init auto-discover."""
|
28
|
+
self.base_view = base_view
|
29
|
+
self.base_url = base_url
|
30
|
+
|
31
|
+
def protect_view(self, view, model):
|
32
|
+
"""
|
33
|
+
Protect view.
|
34
|
+
|
35
|
+
Protects view with login_required if needed, based on model attribute
|
36
|
+
or global settings.
|
37
|
+
"""
|
38
|
+
if getattr(model, 'api_login_required', None) is True:
|
39
|
+
return login_required(view)
|
40
|
+
if getattr(settings, 'TREENODE_API_LOGIN_REQUIRED', False):
|
41
|
+
return login_required(view)
|
42
|
+
return view
|
43
|
+
|
44
|
+
def discover(self):
|
45
|
+
"""Scan models and generate API urls."""
|
46
|
+
urls = [
|
47
|
+
# Admin and Widget end-points
|
48
|
+
path("widget/autocomplete/", TreeNodeAutocompleteView.as_view(), name="tree_autocomplete"), # noqa: D501
|
49
|
+
path("widget/children/", TreeChildrenView.as_view(), name="tree_children"), # noqa: D501
|
50
|
+
path("widget/search/", TreeSearchView.as_view(), name="tree_search"), # noqa: D501
|
51
|
+
]
|
52
|
+
for model in apps.get_models():
|
53
|
+
if issubclass(model, TreeNodeModel) and model is not TreeNodeModel:
|
54
|
+
model_name = model._meta.model_name
|
55
|
+
|
56
|
+
# Dynamically create an API view class for the model
|
57
|
+
api_view_class = type(
|
58
|
+
f"{model_name.capitalize()}APIView",
|
59
|
+
(self.base_view,),
|
60
|
+
{"model": model}
|
61
|
+
)
|
62
|
+
|
63
|
+
# List of API actions and their corresponding URL patterns
|
64
|
+
action_patterns = [
|
65
|
+
# List / Create
|
66
|
+
("", None, f"{model_name}-list"),
|
67
|
+
# Retrieve / Update / Delete
|
68
|
+
("<int:pk>/", None, f"{model_name}-detail"),
|
69
|
+
("tree/", {'action': 'tree'}, f"{model_name}-tree"),
|
70
|
+
# Direct children
|
71
|
+
("<int:pk>/children/", {'action': 'children'}, f"{model_name}-children"), # noqa: D501
|
72
|
+
# All descendants
|
73
|
+
("<int:pk>/descendants/", {'action': 'descendants'}, f"{model_name}-descendants"), # noqa: D501
|
74
|
+
# Ancestors + Self + Descendants
|
75
|
+
("<int:pk>/family/", {'action': 'family'}, f"{model_name}-family"), # noqa: D501
|
76
|
+
]
|
77
|
+
|
78
|
+
# Create secured view instance once
|
79
|
+
view = self.protect_view(api_view_class.as_view(), model)
|
80
|
+
|
81
|
+
# Automatically build all paths for this model
|
82
|
+
for url_suffix, extra_kwargs, route_name in action_patterns:
|
83
|
+
urls.append(
|
84
|
+
path(
|
85
|
+
f"{self.base_url}/{model_name}/{url_suffix}",
|
86
|
+
view,
|
87
|
+
extra_kwargs or {},
|
88
|
+
name=route_name
|
89
|
+
)
|
90
|
+
)
|
91
|
+
return urls
|
treenode/views/autocomplete.py
CHANGED
@@ -1,52 +1,52 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
|
4
|
-
|
5
|
-
Handles autocomplete suggestions for TreeNode models.
|
6
|
-
|
7
|
-
Version: 3.0.0
|
8
|
-
Author: Timur Kady
|
9
|
-
Email: timurkady@yandex.com
|
10
|
-
"""
|
11
|
-
|
12
|
-
# autocomplete.py
|
13
|
-
from django.http import JsonResponse
|
14
|
-
from django.views import View
|
15
|
-
from django.contrib.admin.views.decorators import staff_member_required
|
16
|
-
from django.utils.decorators import method_decorator
|
17
|
-
|
18
|
-
from .common import get_model_from_request
|
19
|
-
|
20
|
-
|
21
|
-
@method_decorator(staff_member_required, name='dispatch')
|
22
|
-
class TreeNodeAutocompleteView(View):
|
23
|
-
"""Widget Autocomplete View."""
|
24
|
-
|
25
|
-
def get(self, request, *args, **kwargs):
|
26
|
-
"""Get request."""
|
27
|
-
select_id = request.GET.get("select_id", "").strip()
|
28
|
-
q = request.GET.get("q", "").strip()
|
29
|
-
|
30
|
-
model = get_model_from_request(request)
|
31
|
-
results = []
|
32
|
-
|
33
|
-
if q:
|
34
|
-
field = getattr(model, "display_field", "id")
|
35
|
-
queryset = model.objects.filter(**{f"{field}__icontains": q})
|
36
|
-
elif select_id:
|
37
|
-
pk = int(select_id)
|
38
|
-
queryset = model.objects.filter(pk=pk)
|
39
|
-
else:
|
40
|
-
queryset = model.objects.filter(parent__isnull=True)
|
41
|
-
|
42
|
-
results = [
|
43
|
-
{
|
44
|
-
"id": obj.pk,
|
45
|
-
"text": str(obj),
|
46
|
-
"level": obj.get_depth(),
|
47
|
-
"is_leaf": obj.is_leaf(),
|
48
|
-
}
|
49
|
-
for obj in queryset[:20]
|
50
|
-
]
|
51
|
-
|
52
|
-
return JsonResponse({"results": results})
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
|
4
|
+
|
5
|
+
Handles autocomplete suggestions for TreeNode models.
|
6
|
+
|
7
|
+
Version: 3.0.0
|
8
|
+
Author: Timur Kady
|
9
|
+
Email: timurkady@yandex.com
|
10
|
+
"""
|
11
|
+
|
12
|
+
# autocomplete.py
|
13
|
+
from django.http import JsonResponse
|
14
|
+
from django.views import View
|
15
|
+
from django.contrib.admin.views.decorators import staff_member_required
|
16
|
+
from django.utils.decorators import method_decorator
|
17
|
+
|
18
|
+
from .common import get_model_from_request
|
19
|
+
|
20
|
+
|
21
|
+
@method_decorator(staff_member_required, name='dispatch')
|
22
|
+
class TreeNodeAutocompleteView(View):
|
23
|
+
"""Widget Autocomplete View."""
|
24
|
+
|
25
|
+
def get(self, request, *args, **kwargs):
|
26
|
+
"""Get request."""
|
27
|
+
select_id = request.GET.get("select_id", "").strip()
|
28
|
+
q = request.GET.get("q", "").strip()
|
29
|
+
|
30
|
+
model = get_model_from_request(request)
|
31
|
+
results = []
|
32
|
+
|
33
|
+
if q:
|
34
|
+
field = getattr(model, "display_field", "id")
|
35
|
+
queryset = model.objects.filter(**{f"{field}__icontains": q})
|
36
|
+
elif select_id:
|
37
|
+
pk = int(select_id)
|
38
|
+
queryset = model.objects.filter(pk=pk)
|
39
|
+
else:
|
40
|
+
queryset = model.objects.filter(parent__isnull=True)
|
41
|
+
|
42
|
+
results = [
|
43
|
+
{
|
44
|
+
"id": obj.pk,
|
45
|
+
"text": str(obj),
|
46
|
+
"level": obj.get_depth(),
|
47
|
+
"is_leaf": obj.is_leaf(),
|
48
|
+
}
|
49
|
+
for obj in queryset[:20]
|
50
|
+
]
|
51
|
+
|
52
|
+
return JsonResponse({"results": results})
|
treenode/views/children.py
CHANGED
@@ -1,41 +1,41 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
|
4
|
-
Version: 3.0.0
|
5
|
-
Author: Timur Kady
|
6
|
-
Email: timurkady@yandex.com
|
7
|
-
"""
|
8
|
-
|
9
|
-
from django.http import JsonResponse
|
10
|
-
from django.views import View
|
11
|
-
from django.contrib.admin.views.decorators import staff_member_required
|
12
|
-
from django.utils.decorators import method_decorator
|
13
|
-
|
14
|
-
from .common import get_model_from_request
|
15
|
-
|
16
|
-
|
17
|
-
@method_decorator(staff_member_required, name='dispatch')
|
18
|
-
class TreeChildrenView(View):
|
19
|
-
def get(self, request, *args, **kwargs):
|
20
|
-
model = get_model_from_request(request)
|
21
|
-
reference_id = request.GET.get("reference_id")
|
22
|
-
if not reference_id:
|
23
|
-
return JsonResponse({"results": []})
|
24
|
-
|
25
|
-
obj = model.objects.filter(pk=reference_id).first()
|
26
|
-
if not obj or obj.is_leaf():
|
27
|
-
return JsonResponse({"results": []})
|
28
|
-
|
29
|
-
results = [
|
30
|
-
{
|
31
|
-
"id": node.pk,
|
32
|
-
"text": str(node),
|
33
|
-
"level": node.get_depth(),
|
34
|
-
"is_leaf": node.is_leaf(),
|
35
|
-
}
|
36
|
-
for node in obj.get_children()
|
37
|
-
]
|
38
|
-
return JsonResponse({"results": results})
|
39
|
-
|
40
|
-
|
41
|
-
# The End
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
|
4
|
+
Version: 3.0.0
|
5
|
+
Author: Timur Kady
|
6
|
+
Email: timurkady@yandex.com
|
7
|
+
"""
|
8
|
+
|
9
|
+
from django.http import JsonResponse
|
10
|
+
from django.views import View
|
11
|
+
from django.contrib.admin.views.decorators import staff_member_required
|
12
|
+
from django.utils.decorators import method_decorator
|
13
|
+
|
14
|
+
from .common import get_model_from_request
|
15
|
+
|
16
|
+
|
17
|
+
@method_decorator(staff_member_required, name='dispatch')
|
18
|
+
class TreeChildrenView(View):
|
19
|
+
def get(self, request, *args, **kwargs):
|
20
|
+
model = get_model_from_request(request)
|
21
|
+
reference_id = request.GET.get("reference_id")
|
22
|
+
if not reference_id:
|
23
|
+
return JsonResponse({"results": []})
|
24
|
+
|
25
|
+
obj = model.objects.filter(pk=reference_id).first()
|
26
|
+
if not obj or obj.is_leaf():
|
27
|
+
return JsonResponse({"results": []})
|
28
|
+
|
29
|
+
results = [
|
30
|
+
{
|
31
|
+
"id": node.pk,
|
32
|
+
"text": str(node),
|
33
|
+
"level": node.get_depth(),
|
34
|
+
"is_leaf": node.is_leaf(),
|
35
|
+
}
|
36
|
+
for node in obj.get_children()
|
37
|
+
]
|
38
|
+
return JsonResponse({"results": results})
|
39
|
+
|
40
|
+
|
41
|
+
# The End
|
treenode/views/common.py
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
"""
|
3
|
-
Created on Thu Apr 10 19:50:23 2025
|
4
|
-
|
5
|
-
Version: 3.0.0
|
6
|
-
Author: Timur Kady
|
7
|
-
Email: timurkady@yandex.com
|
8
|
-
"""
|
9
|
-
|
10
|
-
from django.apps import apps
|
11
|
-
from django.http import Http404
|
12
|
-
|
13
|
-
|
14
|
-
def get_model_from_request(request):
|
15
|
-
"""Get model from request."""
|
16
|
-
model_label = request.GET.get("model")
|
17
|
-
if not model_label:
|
18
|
-
raise Http404("Missing 'model' parameter.")
|
19
|
-
try:
|
20
|
-
app_label, model_name = model_label.lower().split(".")
|
21
|
-
return apps.get_model(app_label, model_name)
|
22
|
-
except Exception:
|
23
|
-
raise Http404(f"Invalid model format: {model_label}")
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
Created on Thu Apr 10 19:50:23 2025
|
4
|
+
|
5
|
+
Version: 3.0.0
|
6
|
+
Author: Timur Kady
|
7
|
+
Email: timurkady@yandex.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
from django.apps import apps
|
11
|
+
from django.http import Http404
|
12
|
+
|
13
|
+
|
14
|
+
def get_model_from_request(request):
|
15
|
+
"""Get model from request."""
|
16
|
+
model_label = request.GET.get("model")
|
17
|
+
if not model_label:
|
18
|
+
raise Http404("Missing 'model' parameter.")
|
19
|
+
try:
|
20
|
+
app_label, model_name = model_label.lower().split(".")
|
21
|
+
return apps.get_model(app_label, model_name)
|
22
|
+
except Exception:
|
23
|
+
raise Http404(f"Invalid model format: {model_label}")
|
treenode/widgets.py
CHANGED
@@ -21,8 +21,6 @@ from django.utils.translation import gettext_lazy as _
|
|
21
21
|
class TreeWidget(forms.Widget):
|
22
22
|
"""Custom widget for hierarchical tree selection."""
|
23
23
|
|
24
|
-
template_name = "django/forms/widgets/select.html"
|
25
|
-
|
26
24
|
class Media:
|
27
25
|
"""Meta class to define required CSS and JS files."""
|
28
26
|
|
{django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
File without changes
|