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.
Files changed (37) hide show
  1. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/METADATA +1 -1
  2. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/RECORD +37 -37
  3. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/WHEEL +1 -1
  4. treenode/__init__.py +1 -0
  5. treenode/admin/admin.py +105 -109
  6. treenode/admin/mixin.py +1 -1
  7. treenode/managers/queries.py +18 -6
  8. treenode/managers/tasks.py +1 -1
  9. treenode/models/models.py +2 -2
  10. treenode/static/css/treenode_admin.css +21 -0
  11. treenode/static/js/treenode_admin.js +130 -339
  12. treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -384
  13. treenode/static/vendors/jquery-ui/LICENSE.txt +43 -43
  14. treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -10716
  15. treenode/static/vendors/jquery-ui/index.html +297 -297
  16. treenode/static/vendors/jquery-ui/jquery-ui.css +438 -438
  17. treenode/static/vendors/jquery-ui/jquery-ui.js +5222 -5222
  18. treenode/static/vendors/jquery-ui/jquery-ui.min.css +6 -6
  19. treenode/static/vendors/jquery-ui/jquery-ui.min.js +5 -5
  20. treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -16
  21. treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +4 -4
  22. treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -439
  23. treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +4 -4
  24. treenode/static/vendors/jquery-ui/package.json +82 -82
  25. treenode/templates/treenode/admin/treenode_changelist.html +61 -15
  26. treenode/templates/treenode/admin/treenode_rows.html +37 -40
  27. treenode/templatetags/treenode_admin.py +57 -14
  28. treenode/utils/db/sqlcompat.py +1 -33
  29. treenode/utils/db/sqlquery.py +0 -24
  30. treenode/version.py +2 -2
  31. treenode/views/autoapi.py +91 -91
  32. treenode/views/autocomplete.py +52 -52
  33. treenode/views/children.py +41 -41
  34. treenode/views/common.py +23 -23
  35. treenode/widgets.py +0 -2
  36. {django_fast_treenode-3.0.6.dist-info → django_fast_treenode-3.0.8.dist-info}/licenses/LICENSE +0 -0
  37. {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
@@ -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})
@@ -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