Skip to content

Commit 0cf76bc

Browse files
authored
Merge pull request #20021 from netbox-community/19999-script-list-widget-misformatted
Fixes #19999: Script list dashboard widget now displays correctly
2 parents 6ce3012 + 9c6d0d1 commit 0cf76bc

File tree

4 files changed

+168
-134
lines changed

4 files changed

+168
-134
lines changed

netbox/extras/tests/test_views.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,21 @@ def test_list_objects_with_permission(self):
807807

808808
def test_list_objects_with_constrained_permission(self):
809809
return
810+
811+
812+
class ScriptListViewTest(TestCase):
813+
user_permissions = ['extras.view_script']
814+
815+
def test_script_list_embedded_parameter(self):
816+
"""Test that ScriptListView accepts embedded parameter without error"""
817+
url = reverse('extras:script_list')
818+
819+
# Test normal request
820+
response = self.client.get(url)
821+
self.assertEqual(response.status_code, 200)
822+
self.assertTemplateUsed(response, 'extras/script_list.html')
823+
824+
# Test embedded request
825+
response = self.client.get(url, {'embedded': 'true'})
826+
self.assertEqual(response.status_code, 200)
827+
self.assertTemplateUsed(response, 'extras/inc/script_list_content.html')

netbox/extras/views.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,11 +1282,18 @@ def get(self, request):
12821282
script_modules = ScriptModule.objects.restrict(request.user).prefetch_related(
12831283
'data_source', 'data_file', 'jobs'
12841284
)
1285-
1286-
return render(request, 'extras/script_list.html', {
1285+
context = {
12871286
'model': ScriptModule,
12881287
'script_modules': script_modules,
1289-
})
1288+
}
1289+
1290+
# Use partial template for dashboard widgets
1291+
template_name = 'extras/script_list.html'
1292+
if request.GET.get('embedded'):
1293+
template_name = 'extras/inc/script_list_content.html'
1294+
context['embedded'] = True
1295+
1296+
return render(request, template_name, context)
12901297

12911298

12921299
class BaseScriptView(generic.ObjectView):
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{% load buttons %}
2+
{% load helpers %}
3+
{% load perms %}
4+
{% load i18n %}
5+
6+
{# Core script list content - used by both full page and embedded views #}
7+
{% for module in script_modules %}
8+
{% include 'inc/sync_warning.html' with object=module %}
9+
<div class="card{% if embedded %} mb-3{% endif %}">
10+
{% if not embedded %}
11+
<h2 class="card-header" id="module{{ module.pk }}">
12+
<i class="mdi mdi-file-document-outline"></i> {{ module }}
13+
<div class="card-actions">
14+
{% if perms.extras.edit_scriptmodule %}
15+
<a href="{% url 'extras:scriptmodule_edit' pk=module.pk %}" class="btn btn-ghost-warning btn-sm">
16+
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit" %}
17+
</a>
18+
{% endif %}
19+
{% if perms.extras.delete_scriptmodule %}
20+
<a href="{% url 'extras:scriptmodule_delete' pk=module.pk %}" class="btn btn-ghost-danger btn-sm">
21+
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
22+
</a>
23+
{% endif %}
24+
</div>
25+
</h2>
26+
{% endif %}
27+
{% with scripts=module.ordered_scripts %}
28+
{% if scripts %}
29+
<table class="table table-hover scripts{% if embedded %} object-list table-sm{% endif %}">
30+
<thead>
31+
<tr>
32+
<th>{% trans "Name" %}</th>
33+
<th>{% trans "Description" %}</th>
34+
<th>{% trans "Last Run" %}</th>
35+
<th>{% trans "Status" %}</th>
36+
<th></th>
37+
</tr>
38+
</thead>
39+
<tbody>
40+
{% for script in scripts %}
41+
{% with last_job=script.get_latest_jobs|first %}
42+
<tr>
43+
<td>
44+
{% if script.is_executable %}
45+
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
46+
{% else %}
47+
<a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
48+
<span class="text-danger">
49+
<i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
50+
</span>
51+
{% endif %}
52+
</td>
53+
<td>{{ script.python_class.description|markdown|placeholder }}</td>
54+
{% if last_job %}
55+
<td>
56+
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
57+
</td>
58+
<td>
59+
{% badge last_job.get_status_display last_job.get_status_color %}
60+
</td>
61+
{% else %}
62+
<td class="text-muted">{% trans "Never" %}</td>
63+
<td>{{ ''|placeholder }}</td>
64+
{% endif %}
65+
<td>
66+
{% if request.user|can_run:script and script.is_executable %}
67+
<div class="float-end d-print-none">
68+
<form action="{% url 'extras:script' script.pk %}" method="post">
69+
{% if script.python_class.commit_default %}
70+
<input type="checkbox" name="_commit" hidden checked>
71+
{% endif %}
72+
{% csrf_token %}
73+
<button type="submit" name="_run" class="btn btn-primary{% if embedded %} btn-sm{% endif %}">
74+
{% if last_job %}
75+
<i class="mdi mdi-replay"></i> {% if not embedded %}{% trans "Run Again" %}{% endif %}
76+
{% else %}
77+
<i class="mdi mdi-play"></i> {% if not embedded %}{% trans "Run Script" %}{% endif %}
78+
{% endif %}
79+
</button>
80+
</form>
81+
</div>
82+
{% endif %}
83+
</td>
84+
</tr>
85+
{% if last_job and not embedded %}
86+
{% for test_name, data in last_job.data.tests.items %}
87+
<tr>
88+
<td colspan="4" class="method">
89+
<span class="ps-3">{{ test_name }}</span>
90+
</td>
91+
<td class="text-end text-nowrap script-stats">
92+
<span class="badge text-bg-success">{{ data.success }}</span>
93+
<span class="badge text-bg-info">{{ data.info }}</span>
94+
<span class="badge text-bg-warning">{{ data.warning }}</span>
95+
<span class="badge text-bg-danger">{{ data.failure }}</span>
96+
</td>
97+
</tr>
98+
{% endfor %}
99+
{% elif last_job and not last_job.data.log and not embedded %}
100+
{# legacy #}
101+
{% for method, stats in last_job.data.items %}
102+
<tr>
103+
<td colspan="4" class="method">
104+
<span class="ps-3">{{ method }}</span>
105+
</td>
106+
<td class="text-end text-nowrap report-stats">
107+
<span class="badge bg-success">{{ stats.success }}</span>
108+
<span class="badge bg-info">{{ stats.info }}</span>
109+
<span class="badge bg-warning">{{ stats.warning }}</span>
110+
<span class="badge bg-danger">{{ stats.failure }}</span>
111+
</td>
112+
</tr>
113+
{% endfor %}
114+
{% endif %}
115+
{% endwith %}
116+
{% endfor %}
117+
</tbody>
118+
</table>
119+
{% else %}
120+
<div class="card-body">
121+
<div class="alert alert-warning" role="alert">
122+
<i class="mdi mdi-alert"></i>
123+
{% blocktrans with module=module.name %}Could not load scripts from module {{ module }}{% endblocktrans %}
124+
</div>
125+
</div>
126+
{% endif %}
127+
{% endwith %}
128+
</div>
129+
{% empty %}
130+
<div class="alert alert-info" role="alert">
131+
<h4 class="alert-heading">{% trans "No Scripts Found" %}</h4>
132+
{% if perms.extras.add_scriptmodule and not embedded %}
133+
{% url 'extras:scriptmodule_add' as create_script_url %}
134+
{% blocktrans trimmed %}
135+
Get started by <a href="{{ create_script_url }}">creating a script</a> from an uploaded file or data source.
136+
{% endblocktrans %}
137+
{% endif %}
138+
</div>
139+
{% endfor %}

netbox/templates/extras/script_list.html

Lines changed: 1 addition & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -19,135 +19,5 @@
1919
{% endblock controls %}
2020

2121
{% block content %}
22-
{% for module in script_modules %}
23-
{% include 'inc/sync_warning.html' with object=module %}
24-
<div class="card">
25-
<h2 class="card-header" id="module{{ module.pk }}">
26-
<i class="mdi mdi-file-document-outline"></i> {{ module }}
27-
<div class="card-actions">
28-
{% if perms.extras.edit_scriptmodule %}
29-
<a href="{% url 'extras:scriptmodule_edit' pk=module.pk %}" class="btn btn-ghost-warning btn-sm">
30-
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit" %}
31-
</a>
32-
{% endif %}
33-
{% if perms.extras.delete_scriptmodule %}
34-
<a href="{% url 'extras:scriptmodule_delete' pk=module.pk %}" class="btn btn-ghost-danger btn-sm">
35-
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
36-
</a>
37-
{% endif %}
38-
</div>
39-
</h2>
40-
{% with scripts=module.ordered_scripts %}
41-
{% if scripts %}
42-
<table class="table table-hover scripts">
43-
<thead>
44-
<tr>
45-
<th>{% trans "Name" %}</th>
46-
<th>{% trans "Description" %}</th>
47-
<th>{% trans "Last Run" %}</th>
48-
<th>{% trans "Status" %}</th>
49-
<th></th>
50-
</tr>
51-
</thead>
52-
<tbody>
53-
{% for script in scripts %}
54-
{% with last_job=script.get_latest_jobs|first %}
55-
<tr>
56-
<td>
57-
{% if script.is_executable %}
58-
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
59-
{% else %}
60-
<a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
61-
<span class="text-danger">
62-
<i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
63-
</span>
64-
{% endif %}
65-
</td>
66-
<td>{{ script.python_class.description|markdown|placeholder }}</td>
67-
{% if last_job %}
68-
<td>
69-
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
70-
</td>
71-
<td>
72-
{% badge last_job.get_status_display last_job.get_status_color %}
73-
</td>
74-
{% else %}
75-
<td class="text-muted">{% trans "Never" %}</td>
76-
<td>{{ ''|placeholder }}</td>
77-
{% endif %}
78-
<td>
79-
{% if request.user|can_run:script and script.is_executable %}
80-
<div class="float-end d-print-none">
81-
<form action="{% url 'extras:script' script.pk %}" method="post">
82-
{% if script.python_class.commit_default %}
83-
<input type="checkbox" name="_commit" hidden checked>
84-
{% endif %}
85-
{% csrf_token %}
86-
<button type="submit" name="_run" class="btn btn-primary btn-sm">
87-
{% if last_job %}
88-
<i class="mdi mdi-replay"></i> {% trans "Run Again" %}
89-
{% else %}
90-
<i class="mdi mdi-play"></i> {% trans "Run Script" %}
91-
{% endif %}
92-
</button>
93-
</form>
94-
</div>
95-
{% endif %}
96-
</td>
97-
</tr>
98-
{% if last_job %}
99-
{% for test_name, data in last_job.data.tests.items %}
100-
<tr>
101-
<td colspan="4" class="method">
102-
<span class="ps-3">{{ test_name }}</span>
103-
</td>
104-
<td class="text-end text-nowrap script-stats">
105-
<span class="badge text-bg-success">{{ data.success }}</span>
106-
<span class="badge text-bg-info">{{ data.info }}</span>
107-
<span class="badge text-bg-warning">{{ data.warning }}</span>
108-
<span class="badge text-bg-danger">{{ data.failure }}</span>
109-
</td>
110-
</tr>
111-
{% endfor %}
112-
{% elif not last_job.data.log %}
113-
{# legacy #}
114-
{% for method, stats in last_job.data.items %}
115-
<tr>
116-
<td colspan="4" class="method">
117-
<span class="ps-3">{{ method }}</span>
118-
</td>
119-
<td class="text-end text-nowrap report-stats">
120-
<span class="badge bg-success">{{ stats.success }}</span>
121-
<span class="badge bg-info">{{ stats.info }}</span>
122-
<span class="badge bg-warning">{{ stats.warning }}</span>
123-
<span class="badge bg-danger">{{ stats.failure }}</span>
124-
</td>
125-
</tr>
126-
{% endfor %}
127-
{% endif %}
128-
{% endwith %}
129-
{% endfor %}
130-
</tbody>
131-
</table>
132-
{% else %}
133-
<div class="card-body">
134-
<div class="alert alert-warning" role="alert">
135-
<i class="mdi mdi-alert"></i>
136-
{% blocktrans with module=module.name %}Could not load scripts from module {{ module }}{% endblocktrans %}
137-
</div>
138-
</div>
139-
{% endif %}
140-
{% endwith %}
141-
</div>
142-
{% empty %}
143-
<div class="alert alert-info" role="alert">
144-
<h4 class="alert-heading">{% trans "No Scripts Found" %}</h4>
145-
{% if perms.extras.add_scriptmodule %}
146-
{% url 'extras:scriptmodule_add' as create_script_url %}
147-
{% blocktrans trimmed %}
148-
Get started by <a href="{{ create_script_url }}">creating a script</a> from an uploaded file or data source.
149-
{% endblocktrans %}
150-
{% endif %}
151-
</div>
152-
{% endfor %}
22+
{% include 'extras/inc/script_list_content.html' with embedded=False %}
15323
{% endblock content %}

0 commit comments

Comments
 (0)