django-boosted 0.1.1__tar.gz

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 (46) hide show
  1. django_boosted-0.1.1/LICENSE +23 -0
  2. django_boosted-0.1.1/PKG-INFO +138 -0
  3. django_boosted-0.1.1/README.md +104 -0
  4. django_boosted-0.1.1/pyproject.toml +83 -0
  5. django_boosted-0.1.1/setup.cfg +4 -0
  6. django_boosted-0.1.1/src/django_boosted/__init__.py +20 -0
  7. django_boosted-0.1.1/src/django_boosted/admin/__init__.py +5 -0
  8. django_boosted-0.1.1/src/django_boosted/admin/fieldsets.py +44 -0
  9. django_boosted-0.1.1/src/django_boosted/admin/format.py +90 -0
  10. django_boosted-0.1.1/src/django_boosted/admin/model.py +198 -0
  11. django_boosted-0.1.1/src/django_boosted/admin/tools.py +55 -0
  12. django_boosted-0.1.1/src/django_boosted/admin/urls.py +24 -0
  13. django_boosted-0.1.1/src/django_boosted/admin/views/__init__.py +7 -0
  14. django_boosted-0.1.1/src/django_boosted/admin/views/adminform.py +186 -0
  15. django_boosted-0.1.1/src/django_boosted/admin/views/base.py +144 -0
  16. django_boosted-0.1.1/src/django_boosted/admin/views/confirm.py +102 -0
  17. django_boosted-0.1.1/src/django_boosted/admin/views/form.py +40 -0
  18. django_boosted-0.1.1/src/django_boosted/admin/views/generator.py +23 -0
  19. django_boosted-0.1.1/src/django_boosted/admin/views/json.py +61 -0
  20. django_boosted-0.1.1/src/django_boosted/admin/views/list.py +156 -0
  21. django_boosted-0.1.1/src/django_boosted/admin/views/message.py +39 -0
  22. django_boosted-0.1.1/src/django_boosted/admin/views/redirect.py +82 -0
  23. django_boosted-0.1.1/src/django_boosted/admin/views/setup.py +54 -0
  24. django_boosted-0.1.1/src/django_boosted/apps.py +21 -0
  25. django_boosted-0.1.1/src/django_boosted/decorators.py +61 -0
  26. django_boosted-0.1.1/src/django_boosted/managers/__init__.py +3 -0
  27. django_boosted-0.1.1/src/django_boosted/managers/urls.py +122 -0
  28. django_boosted-0.1.1/src/django_boosted/models/__init__.py +0 -0
  29. django_boosted-0.1.1/src/django_boosted/models/urls.py +36 -0
  30. django_boosted-0.1.1/src/django_boosted/rest_framework/__init__.py +10 -0
  31. django_boosted-0.1.1/src/django_boosted/rest_framework/metadata.py +25 -0
  32. django_boosted-0.1.1/src/django_boosted/static/admin_boost/admin_boost.css +153 -0
  33. django_boosted-0.1.1/src/django_boosted/templates/admin/submit_line.html +10 -0
  34. django_boosted-0.1.1/src/django_boosted/templates/admin_boost/admin_boost_form.html +45 -0
  35. django_boosted-0.1.1/src/django_boosted/templates/admin_boost/change_form.html +25 -0
  36. django_boosted-0.1.1/src/django_boosted/templates/admin_boost/change_list.html +28 -0
  37. django_boosted-0.1.1/src/django_boosted/templates/admin_boost/confirm.html +33 -0
  38. django_boosted-0.1.1/src/django_boosted/templates/admin_boost/message.html +26 -0
  39. django_boosted-0.1.1/src/django_boosted/templatetags/__init__.py +1 -0
  40. django_boosted-0.1.1/src/django_boosted/templatetags/boosted_tags.py +19 -0
  41. django_boosted-0.1.1/src/django_boosted.egg-info/PKG-INFO +138 -0
  42. django_boosted-0.1.1/src/django_boosted.egg-info/SOURCES.txt +44 -0
  43. django_boosted-0.1.1/src/django_boosted.egg-info/dependency_links.txt +1 -0
  44. django_boosted-0.1.1/src/django_boosted.egg-info/requires.txt +10 -0
  45. django_boosted-0.1.1/src/django_boosted.egg-info/top_level.txt +1 -0
  46. django_boosted-0.1.1/tests/test_views.py +66 -0
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Octolo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-boosted
3
+ Version: 0.1.1
4
+ Summary: Reusable helpers to register custom Django admin views and object tools.
5
+ Author-email: Octolo <dev@octolo.tech>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/octolo/django-boosted
8
+ Project-URL: Repository, https://github.com/octolo/django-boosted
9
+ Project-URL: Documentation, https://github.com/octolo/django-boosted#readme
10
+ Project-URL: Issues, https://github.com/octolo/django-boosted/issues
11
+ Keywords: django,django-admin,admin-views,admin-tools,django-extensions,admin-customization,python
12
+ Classifier: Framework :: Django
13
+ Classifier: Framework :: Django :: 4.2
14
+ Classifier: Framework :: Django :: 5.0
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: Django>=4.2
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.4; extra == "dev"
27
+ Requires-Dist: pytest-django>=4.8; extra == "dev"
28
+ Requires-Dist: ruff>=0.7.0; extra == "dev"
29
+ Requires-Dist: black>=24.8; extra == "dev"
30
+ Requires-Dist: bandit>=1.7; extra == "dev"
31
+ Requires-Dist: safety>=3.2; extra == "dev"
32
+ Requires-Dist: pip-audit>=2.7; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # django-boosted
36
+
37
+ Lightweight helpers to extend Django’s admin with extra views, custom forms, and the matching UI affordances (object tools, permissions, standard responses).
38
+
39
+ ## Features
40
+
41
+ - **`@admin_boost_object_view` decorator** – fetches the target object, checks permissions, and builds the default context before rendering your template.
42
+ - **`AdminBoostMixin`** – registers the custom URLs, protects them with `admin_site.admin_view`, and injects extra object-tool buttons into the change form.
43
+ - **Additional templates** – a change form template that renders the injected buttons plus a simple “Hello” view as a teaching aid.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install django-boosted
49
+ ```
50
+
51
+ ## Quick start
52
+
53
+ ```python
54
+ # app/admin.py
55
+ from django.contrib import admin
56
+ from django_boosted.mixins import AdminBoostMixin
57
+ from django_boosted.decorators import admin_boost_object_view
58
+ from .models import Client
59
+
60
+
61
+ class ClientAdmin(AdminBoostMixin, admin.ModelAdmin):
62
+ boost_views = ["hello_view"]
63
+ change_form_template = "admin_boost/change_form.html"
64
+
65
+ @admin_boost_object_view(label="Say hello", template_name="admin_boost/hello.html")
66
+ def hello_view(self, request, obj):
67
+ return {"message": f"Hello {obj}!"}
68
+
69
+
70
+ admin.site.register(Client, ClientAdmin)
71
+ ```
72
+
73
+ Include the provided templates in your `TEMPLATES["DIRS"]` (or copy them to customize).
74
+
75
+ ## Using forms with ForeignKey widgets
76
+
77
+ The decorator can automatically apply admin widgets (`ForeignKeyRawIdWidget` or `AutocompleteSelect`) to your form fields, using the same logic as `ModelAdmin.change_view()`:
78
+
79
+ ```python
80
+ # app/admin.py
81
+ from django import forms
82
+ from django.contrib import admin, messages
83
+ from django.shortcuts import redirect
84
+ from django_boosted.mixins import AdminBoostMixin
85
+ from django_boosted.decorators import admin_boost_object_view
86
+ from .models import Company
87
+
88
+ class SyncFullGroupForm(forms.Form):
89
+ group = forms.ModelChoiceField(queryset=Company.objects.all())
90
+ option = forms.ChoiceField(
91
+ label="sync method",
92
+ choices=[("method1", "Method 1"), ("method2", "Method 2")],
93
+ )
94
+
95
+ class CompanyAdmin(AdminBoostMixin, admin.ModelAdmin):
96
+ boost_views = ["sync_full_group_view"]
97
+ change_form_template = "admin_boost/change_form.html"
98
+
99
+ @admin_boost_object_view(
100
+ label="Sync Full Group",
101
+ template_name="admin/sync_full_group.html",
102
+ form=SyncFullGroupForm,
103
+ raw_id_fields=["group"], # Automatically applies ForeignKeyRawIdWidget
104
+ )
105
+ def sync_full_group_view(self, request, obj, form):
106
+ if request.method == "POST" and form.is_valid():
107
+ # Process form...
108
+ group = form.cleaned_data["group"]
109
+ option = form.cleaned_data["option"]
110
+ # ... your logic ...
111
+ self.message_user(request, "Sync completed", messages.SUCCESS)
112
+ return redirect(obj.admin_change_url)
113
+ return {} # Return additional context if needed
114
+ ```
115
+
116
+ The decorator handles:
117
+ - Widget application (respects `raw_id_fields` and `autocomplete_fields`)
118
+ - Form validation on POST
119
+ - Adding the form to the template context
120
+ - Backward compatibility (if your view doesn't accept a `form` parameter, it won't be passed)
121
+
122
+ ## Development commands
123
+
124
+ Run everything via `./service.py dev <command>` or `python dev.py <command>`:
125
+
126
+ | Command | Description |
127
+ | --- | --- |
128
+ | `./service.py dev install-dev` or `python dev.py install-dev` | create the venv and install the package editable with `dev` extras. |
129
+ | `./service.py dev lint` or `python dev.py lint` | run Ruff + Black in check mode. |
130
+ | `./service.py dev format` or `python dev.py format` | apply Ruff --fix then Black. |
131
+ | `./service.py dev test` or `python dev.py test` | run `pytest` (with `pytest-django`). |
132
+ | `./service.py dev build` or `python dev.py build` | clean then build wheel + sdist. |
133
+ | `./service.py quality security` or `python dev.py security` | Bandit + Safety + pip-audit. |
134
+ | `./service.py dev help` or `python dev.py help` | list all commands. |
135
+
136
+ ## License
137
+
138
+ MIT — see the `LICENSE` file. Contributions welcome!
@@ -0,0 +1,104 @@
1
+ # django-boosted
2
+
3
+ Lightweight helpers to extend Django’s admin with extra views, custom forms, and the matching UI affordances (object tools, permissions, standard responses).
4
+
5
+ ## Features
6
+
7
+ - **`@admin_boost_object_view` decorator** – fetches the target object, checks permissions, and builds the default context before rendering your template.
8
+ - **`AdminBoostMixin`** – registers the custom URLs, protects them with `admin_site.admin_view`, and injects extra object-tool buttons into the change form.
9
+ - **Additional templates** – a change form template that renders the injected buttons plus a simple “Hello” view as a teaching aid.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install django-boosted
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```python
20
+ # app/admin.py
21
+ from django.contrib import admin
22
+ from django_boosted.mixins import AdminBoostMixin
23
+ from django_boosted.decorators import admin_boost_object_view
24
+ from .models import Client
25
+
26
+
27
+ class ClientAdmin(AdminBoostMixin, admin.ModelAdmin):
28
+ boost_views = ["hello_view"]
29
+ change_form_template = "admin_boost/change_form.html"
30
+
31
+ @admin_boost_object_view(label="Say hello", template_name="admin_boost/hello.html")
32
+ def hello_view(self, request, obj):
33
+ return {"message": f"Hello {obj}!"}
34
+
35
+
36
+ admin.site.register(Client, ClientAdmin)
37
+ ```
38
+
39
+ Include the provided templates in your `TEMPLATES["DIRS"]` (or copy them to customize).
40
+
41
+ ## Using forms with ForeignKey widgets
42
+
43
+ The decorator can automatically apply admin widgets (`ForeignKeyRawIdWidget` or `AutocompleteSelect`) to your form fields, using the same logic as `ModelAdmin.change_view()`:
44
+
45
+ ```python
46
+ # app/admin.py
47
+ from django import forms
48
+ from django.contrib import admin, messages
49
+ from django.shortcuts import redirect
50
+ from django_boosted.mixins import AdminBoostMixin
51
+ from django_boosted.decorators import admin_boost_object_view
52
+ from .models import Company
53
+
54
+ class SyncFullGroupForm(forms.Form):
55
+ group = forms.ModelChoiceField(queryset=Company.objects.all())
56
+ option = forms.ChoiceField(
57
+ label="sync method",
58
+ choices=[("method1", "Method 1"), ("method2", "Method 2")],
59
+ )
60
+
61
+ class CompanyAdmin(AdminBoostMixin, admin.ModelAdmin):
62
+ boost_views = ["sync_full_group_view"]
63
+ change_form_template = "admin_boost/change_form.html"
64
+
65
+ @admin_boost_object_view(
66
+ label="Sync Full Group",
67
+ template_name="admin/sync_full_group.html",
68
+ form=SyncFullGroupForm,
69
+ raw_id_fields=["group"], # Automatically applies ForeignKeyRawIdWidget
70
+ )
71
+ def sync_full_group_view(self, request, obj, form):
72
+ if request.method == "POST" and form.is_valid():
73
+ # Process form...
74
+ group = form.cleaned_data["group"]
75
+ option = form.cleaned_data["option"]
76
+ # ... your logic ...
77
+ self.message_user(request, "Sync completed", messages.SUCCESS)
78
+ return redirect(obj.admin_change_url)
79
+ return {} # Return additional context if needed
80
+ ```
81
+
82
+ The decorator handles:
83
+ - Widget application (respects `raw_id_fields` and `autocomplete_fields`)
84
+ - Form validation on POST
85
+ - Adding the form to the template context
86
+ - Backward compatibility (if your view doesn't accept a `form` parameter, it won't be passed)
87
+
88
+ ## Development commands
89
+
90
+ Run everything via `./service.py dev <command>` or `python dev.py <command>`:
91
+
92
+ | Command | Description |
93
+ | --- | --- |
94
+ | `./service.py dev install-dev` or `python dev.py install-dev` | create the venv and install the package editable with `dev` extras. |
95
+ | `./service.py dev lint` or `python dev.py lint` | run Ruff + Black in check mode. |
96
+ | `./service.py dev format` or `python dev.py format` | apply Ruff --fix then Black. |
97
+ | `./service.py dev test` or `python dev.py test` | run `pytest` (with `pytest-django`). |
98
+ | `./service.py dev build` or `python dev.py build` | clean then build wheel + sdist. |
99
+ | `./service.py quality security` or `python dev.py security` | Bandit + Safety + pip-audit. |
100
+ | `./service.py dev help` or `python dev.py help` | list all commands. |
101
+
102
+ ## License
103
+
104
+ MIT — see the `LICENSE` file. Contributions welcome!
@@ -0,0 +1,83 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "django-boosted"
7
+ version = "0.1.1"
8
+ description = "Reusable helpers to register custom Django admin views and object tools."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ {name = "Octolo", email = "dev@octolo.tech"}
15
+ ]
16
+ keywords = [
17
+ "django",
18
+ "django-admin",
19
+ "admin-views",
20
+ "admin-tools",
21
+ "django-extensions",
22
+ "admin-customization",
23
+ "python",
24
+ ]
25
+ dependencies = [
26
+ "Django>=4.2",
27
+ ]
28
+ classifiers = [
29
+ "Framework :: Django",
30
+ "Framework :: Django :: 4.2",
31
+ "Framework :: Django :: 5.0",
32
+ "Intended Audience :: Developers",
33
+ "Programming Language :: Python :: 3",
34
+ "Programming Language :: Python :: 3.11",
35
+ "Programming Language :: Python :: 3.12",
36
+ "Programming Language :: Python :: 3 :: Only",
37
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/octolo/django-boosted"
42
+ Repository = "https://github.com/octolo/django-boosted"
43
+ Documentation = "https://github.com/octolo/django-boosted#readme"
44
+ Issues = "https://github.com/octolo/django-boosted/issues"
45
+
46
+ [project.optional-dependencies]
47
+ dev = [
48
+ "pytest>=7.4",
49
+ "pytest-django>=4.8",
50
+ "ruff>=0.7.0",
51
+ "black>=24.8",
52
+ "bandit>=1.7",
53
+ "safety>=3.2",
54
+ "pip-audit>=2.7",
55
+ ]
56
+
57
+ [tool.setuptools]
58
+ package-dir = {"" = "src"}
59
+
60
+ [tool.setuptools.packages.find]
61
+ where = ["src"]
62
+
63
+ [tool.setuptools.package-data]
64
+ django_boosted = ["templates/**/*", "static/**/*"]
65
+
66
+ [tool.pytest.ini_options]
67
+ DJANGO_SETTINGS_MODULE = "tests.settings"
68
+ pythonpath = ["src", "."]
69
+ addopts = "-ra"
70
+
71
+ [tool.black]
72
+ line-length = 88
73
+ target-version = ["py311"]
74
+
75
+ [tool.ruff]
76
+ line-length = 88
77
+ target-version = "py311"
78
+ extend-exclude = ["**/migrations/*"]
79
+
80
+ [tool.ruff.lint]
81
+ select = ["E", "F", "I"]
82
+
83
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """Public exports for django-boosted."""
2
+ default_app_config = "django_boosted.apps.DjangoBoostedConfig" # noqa: E402
3
+
4
+ from .admin import AdminBoostModel, AdminBoostFormat # noqa: E402
5
+ from .decorators import admin_boost_action, admin_boost_view # noqa: E402
6
+
7
+ __all__ = [
8
+ "AdminBoostModel",
9
+ "AdminBoostFormat",
10
+ "admin_boost_action",
11
+ "admin_boost_view",
12
+ ]
13
+
14
+ try:
15
+ from django_boosted.rest_framework.metadata import BoostedRestFrameworkMetadata
16
+ __all__.append("BoostedRestFrameworkMetadata")
17
+ except ImportError:
18
+ pass # rest_framework not installed
19
+
20
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Admin module for django-boosted."""
2
+
3
+ from .model import AdminBoostModel, AdminBoostFormat
4
+
5
+ __all__ = ["AdminBoostModel", "AdminBoostFormat"]
@@ -0,0 +1,44 @@
1
+ from typing import Iterable
2
+
3
+
4
+ def add_to_fieldset(self, name: str, fields: Iterable[str], **kwargs):
5
+ """Add fields to a fieldset by name. Create the fieldset if it doesn't exist."""
6
+ if self.fieldsets is None:
7
+ self.fieldsets = []
8
+
9
+ fieldset_dict = None
10
+
11
+ for fieldset in self.fieldsets:
12
+ if fieldset[0] == name:
13
+ fieldset_dict = fieldset[1]
14
+ break
15
+
16
+ if fieldset_dict is None:
17
+ fieldset_dict = {"fields": [],}
18
+ self.fieldsets.append((name, fieldset_dict))
19
+ else:
20
+ if "fields" not in fieldset_dict:
21
+ fieldset_dict["fields"] = []
22
+ elif isinstance(fieldset_dict["fields"], tuple):
23
+ fieldset_dict["fields"] = list(fieldset_dict["fields"])
24
+
25
+ for field in fields:
26
+ if field not in fieldset_dict["fields"]:
27
+ fieldset_dict["fields"].append(field)
28
+
29
+ if kwargs.get("classes"):
30
+ fieldset_dict["classes"] = kwargs.get("classes")
31
+
32
+ def remove_from_fieldset(self, name: str, fields: Iterable[str]):
33
+ """Remove fields from a fieldset by name."""
34
+ if self.fieldsets is None:
35
+ return
36
+
37
+ for fieldset in self.fieldsets:
38
+ if fieldset[0] == name:
39
+ fieldset_dict = fieldset[1]
40
+ if "fields" in fieldset_dict:
41
+ for field in fields:
42
+ if field in fieldset_dict["fields"]:
43
+ fieldset_dict["fields"].remove(field)
44
+ break
@@ -0,0 +1,90 @@
1
+ """Formatting utilities for django-boosted."""
2
+
3
+ from django.utils.html import format_html
4
+ from django.templatetags.static import static
5
+ from django.utils.translation import gettext_lazy as _
6
+
7
+ def boolean_icon_html(value):
8
+ """Return the HTML image (admin icon) for a boolean value."""
9
+ is_ok = value == "✓" if isinstance(value, str) else bool(value)
10
+ icon = "icon-yes.svg" if is_ok else "icon-no.svg"
11
+ return format_html(
12
+ '<img src="{}" alt="{}">',
13
+ static(f"admin/img/{icon}"),
14
+ _("Yes") if is_ok else _("No"),
15
+ )
16
+
17
+
18
+ def format_label(
19
+ text: str,
20
+ label_type: str = "info",
21
+ size: str | None = None,
22
+ link: str | None = None,
23
+ style: str | None = None,
24
+ ) -> str:
25
+ classes = ["boost-label"]
26
+ valid_types = [
27
+ "success",
28
+ "info",
29
+ "warning",
30
+ "danger",
31
+ "primary",
32
+ "secondary",
33
+ "default",
34
+ ]
35
+ if label_type.lower() in valid_types:
36
+ classes.append(label_type.lower())
37
+ else:
38
+ classes.append("info")
39
+ if size and size.lower() in ["small", "big"]:
40
+ classes.append(size.lower())
41
+ if link:
42
+ classes.append("link")
43
+ css_class = " ".join(classes)
44
+ tag = "a" if link else "span"
45
+ if link and style:
46
+ return format_html(
47
+ '<{} href="{}" class="{}" style="{}">{}</{}>',
48
+ tag,
49
+ link,
50
+ css_class,
51
+ style,
52
+ text,
53
+ tag,
54
+ )
55
+ if link:
56
+ return format_html(
57
+ '<{} href="{}" class="{}">{}</{}>', tag, link, css_class, text, tag
58
+ )
59
+ if style:
60
+ return format_html(
61
+ '<{} class="{}" style="{}">{}</{}>', tag, css_class, style, text, tag
62
+ )
63
+ return format_html('<{} class="{}">{}</{}>', tag, css_class, text, tag)
64
+
65
+
66
+ def format_status(
67
+ name: str, status: bool, style: str | None = None, link: str | None = None
68
+ ) -> str:
69
+ icon = "✓" if status else "✗"
70
+ status_class = "success" if status else "error"
71
+ tag = "a" if link else "span"
72
+ return format_html(
73
+ '<{} href="{}"><span class="boost-status {}" style="{}">{}</span> '
74
+ '<code>{}</code></{}>',
75
+ tag,
76
+ link,
77
+ status_class,
78
+ style,
79
+ icon,
80
+ name,
81
+ tag,
82
+ )
83
+
84
+
85
+ def format_with_help_text(html_content: str, help_text: str | None = None) -> str:
86
+ if help_text:
87
+ return format_html(
88
+ '{}<br><small class="help">{}</small>', html_content, help_text
89
+ )
90
+ return html_content