Skip to content

Commit e6ccdc3

Browse files
authored
Merge pull request #306 from usnistgov/7.3.3.dev
7.3.3.dev
2 parents d93fc95 + 9c5149f commit e6ccdc3

28 files changed

+456
-109
lines changed

NEMO/admin.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
ToolUsageQuestions,
116116
ToolWaitList,
117117
TrainingSession,
118+
UnplannedOutage,
118119
UsageEvent,
119120
User,
120121
UserDocuments,
@@ -271,7 +272,17 @@ class ToolAdmin(admin.ModelAdmin):
271272
},
272273
),
273274
("Approval", {"fields": ("_adjustment_request_reviewers",)}),
274-
("Reservation", {"fields": ("_reservation_horizon", "_missed_reservation_threshold", "_abuse_weight")}),
275+
(
276+
"Reservation",
277+
{
278+
"fields": (
279+
"_reservation_horizon",
280+
"_missed_reservation_threshold",
281+
"_late_cancellation_reservation_threshold",
282+
"_abuse_weight",
283+
)
284+
},
285+
),
275286
(
276287
"Usage policy",
277288
{
@@ -514,7 +525,17 @@ class AreaAdmin(DraggableMPTTAdmin):
514525
},
515526
),
516527
("Approval", {"fields": ("adjustment_request_reviewers", "access_request_reviewers")}),
517-
("Reservation", {"fields": ("reservation_horizon", "missed_reservation_threshold", "abuse_weight")}),
528+
(
529+
"Reservation",
530+
{
531+
"fields": (
532+
"reservation_horizon",
533+
"missed_reservation_threshold",
534+
"late_cancellation_reservation_threshold",
535+
"abuse_weight",
536+
)
537+
},
538+
),
518539
(
519540
"Policy",
520541
{
@@ -811,7 +832,7 @@ class ReservationAdmin(ObjPermissionAdminMixin, ModelAdminRedirectMixin, admin.M
811832
"project",
812833
"start",
813834
"end",
814-
"duration",
835+
"duration_rounded",
815836
"cancelled",
816837
"missed",
817838
"shortened",
@@ -1801,6 +1822,24 @@ class ScheduledOutageAdmin(admin.ModelAdmin):
18011822
("creator", admin.RelatedOnlyFieldListFilter),
18021823
)
18031824
autocomplete_fields = ["tool", "creator"]
1825+
date_hierarchy = "start"
1826+
1827+
1828+
@register(UnplannedOutage)
1829+
class UnplannedOutageAdmin(admin.ModelAdmin):
1830+
list_display = ("id", "tool", "start", "end")
1831+
list_filter = (("tool", admin.RelatedOnlyFieldListFilter),)
1832+
autocomplete_fields = ["tool"]
1833+
date_hierarchy = "start"
1834+
1835+
def has_delete_permission(self, request, obj=None):
1836+
return request.user.is_superuser
1837+
1838+
def has_add_permission(self, request):
1839+
return request.user.is_superuser
1840+
1841+
def has_change_permission(self, request, obj=None):
1842+
return request.user.is_superuser
18041843

18051844

18061845
@register(News)

NEMO/apps/area_access/templates/area_access/base.html

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,7 @@
3535
<div id="area-container">
3636
<div id="default_content">
3737
{% block default_content %}{% endblock %}
38-
<div id="occupancy"></div>
39-
<div id="usage_title"></div>
40-
<div class="tab-pane{% if tools_exist and tab == 'tools' %} active{% endif %} info-tooltip-container"
41-
id="tools">
42-
<div id="usage"></div>
43-
</div>
44-
<div id="alerts"></div>
38+
<div id="dynamic_content"></div>
4539
</div>
4640
<div id="error" style="display: none">
4741
<h1>There was a problem communicating with the web server. Please visit the user office for assistance.</h1>
@@ -82,12 +76,26 @@ <h1 style="color:lightgrey" id="badge_number"></h1>
8276
{% with show_badge_number=customizations|get_item:"show_badge_number" %}
8377
let area_access_badge_reader = new BadgeReader(send_badge_number, "{{ badge_reader.send_key }}", {% if badge_reader.record_key %}"{{ badge_reader.record_key }}",{% else %}undefined,{% endif %} {{ show_badge_number|yesno:"true,false" }});
8478
{% endwith %}
85-
let timeout_handle = null;
79+
let timeout_handle = null;
8680
let countdown_handle = null;
81+
82+
// Reorder divs based on URL parameter order
83+
const dynamic_container = $("#dynamic_content");
8784
const current_url_parameters = new URLSearchParams(window.location.search);
85+
86+
for (const [key, value] of current_url_parameters.entries()) {
87+
if (key === 'occupancy') {
88+
dynamic_container.append('<div id="occupancy"></div>');
89+
} else if (key === 'usage') {
90+
dynamic_container.append('<div id="usage_title"></div><div class="info-tooltip-container" id="tools"><div id="usage"></div></div>');
91+
} else if (key === 'alerts') {
92+
dynamic_container.append('<div id="alerts"></div>');
93+
}
94+
}
95+
8896
let occupancy = current_url_parameters.get('occupancy');
89-
let usage = new URLSearchParams(window.location.search).get('usage') !== null;
90-
let alerts = new URLSearchParams(window.location.search).get('alerts') !== null;
97+
let usage = current_url_parameters.get('usage') !== null;
98+
let alerts = current_url_parameters.get('alerts') !== null;
9199

92100
let default_content_element = $("#default_content");
93101
let status_element = $("#status");
@@ -145,9 +153,9 @@ <h1 style="color:lightgrey" id="badge_number"></h1>
145153
function revert_to_default_content()
146154
{
147155
$('body').removeClass();
148-
fetch_occupancy();
149-
fetch_usage();
150-
fetch_alerts();
156+
if (occupancy) fetch_occupancy();
157+
if (usage) fetch_usage();
158+
if (alerts) fetch_alerts();
151159
clear_timeout();
152160
status_element.hide();
153161
error_element.hide();

NEMO/apps/kiosk/templates/kiosk/kiosk.html

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,7 @@
4545
<div class="container-fluid">
4646
<div id="default_content">
4747
{{ customizations|get_item:"kiosk_message"|default_if_none:"<h1>Scan your badge to control tools</h1>"|safe }}
48-
<div id="occupancy"></div>
49-
<div id="usage_title"></div>
50-
<div class="tab-pane{% if tools_exist and tab == 'tools' %} active{% endif %} info-tooltip-container"
51-
id="tools">
52-
<div id="usage"></div>
53-
</div>
54-
<div id="alerts"></div>
48+
<div id="dynamic_content"></div>
5549
</div>
5650
<div id="error" style="display: none">
5751
<h1>There was a problem communicating with the web server. Please visit the user office for assistance.</h1>
@@ -100,7 +94,21 @@ <h1 style="color:lightgrey" id="badge_number"></h1>
10094
{% endwith %}
10195
let timeout_handle = null;
10296
let countdown_handle = null;
97+
98+
// Reorder divs based on URL parameter order
99+
const dynamic_container = $("#dynamic_content");
103100
const current_url_parameters = new URLSearchParams(window.location.search);
101+
102+
for (const [key, value] of current_url_parameters.entries()) {
103+
if (key === 'occupancy') {
104+
dynamic_container.append('<div id="occupancy"></div>');
105+
} else if (key === 'usage') {
106+
dynamic_container.append('<div id="usage_title"></div><div class="info-tooltip-container" id="tools"><div id="usage"></div></div>');
107+
} else if (key === 'alerts') {
108+
dynamic_container.append('<div id="alerts"></div>');
109+
}
110+
}
111+
104112
const occupancy = current_url_parameters.get("occupancy");
105113
const usage = current_url_parameters.get("usage") !== null;
106114
const alerts = current_url_parameters.get("alerts") !== null;
@@ -184,9 +192,9 @@ <h1 style="color:lightgrey" id="badge_number"></h1>
184192
}
185193
function revert_to_default_content()
186194
{
187-
fetch_occupancy();
188-
fetch_usage();
189-
fetch_alerts();
195+
if (occupancy) fetch_occupancy();
196+
if (usage) fetch_usage();
197+
if (alerts) fetch_alerts();
190198
clear_timeout();
191199
close_numpads();
192200
close_keyboards();
@@ -487,10 +495,7 @@ <h1 style="color:lightgrey" id="badge_number"></h1>
487495
}
488496
function fetch_occupancy()
489497
{
490-
if(occupancy)
491-
{
492-
ajax_get('{% url 'kiosk_occupancy' %}', {'occupancy': occupancy}, function(response) { $("#occupancy").html(response); });
493-
}
498+
ajax_get('{% url 'kiosk_occupancy' %}', {'occupancy': occupancy}, function(response) { $("#occupancy").html(response); });
494499
}
495500
function logout_user(tool_id, badge_number)
496501
{
@@ -499,25 +504,19 @@ <h1 style="color:lightgrey" id="badge_number"></h1>
499504
}
500505
function fetch_usage()
501506
{
502-
if(usage)
503-
{
504-
$("#usage_title").html("<h2>Tools in use</h2>")
505-
ajax_get('{% url 'kiosk_usage' %}', {'interest': 'tools'},
506-
function(response) {
507-
$("#usage").html(response);
508-
$(".in_use").css("display", "table-row");
509-
$(".idle").hide();
510-
$(".glyphicon-remove-circle").hide();
511-
}
512-
);
513-
}
507+
$("#usage_title").html("<h2>Tools in use</h2>")
508+
ajax_get('{% url 'kiosk_usage' %}', {'interest': 'tools'},
509+
function(response) {
510+
$("#usage").html(response);
511+
$(".in_use").css("display", "table-row");
512+
$(".idle").hide();
513+
$(".glyphicon-remove-circle").hide();
514+
}
515+
);
514516
}
515517
function fetch_alerts()
516518
{
517-
if(alerts)
518-
{
519-
$("#alerts").load("{% url 'kiosk_alerts' %}");
520-
}
519+
$("#alerts").load("{% url 'kiosk_alerts' %}");
521520
}
522521
function close_numpads()
523522
{

NEMO/apps/kiosk/templates/kiosk/tool_report_problem.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ <h4>{{ message }}</h4>
100100
<span style="padding-left:10px;">Shut down the tool so that it may not be used until this problem is resolved.</span>
101101
</label>
102102
</div>
103-
{% if customer|is_staff_on_tool:tool and tool.interlock %}
103+
{% if customer|is_staff_on_tool:tool and tool.interlock and customizations|get_item:"tool_interlock_ask_when_shutting_down_problem" == "enabled" %}
104104
<div class="checkbox" id="tool_problem_lock">
105105
<label>
106106
<input name="lock" type="checkbox" {% if form.lock.value %}checked{% endif %} />
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 4.2.27 on 2026-01-19 17:12
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("NEMO", "0137_area_nemo_area_tree_id_lft_idx"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="area",
15+
name="late_cancellation_reservation_threshold",
16+
field=models.PositiveIntegerField(
17+
blank=True,
18+
db_column="late_cancellation_reservation_threshold",
19+
help_text='The amount of time (in minutes) before its start time that a area reservation may be canceled before it is automatically marked as "missed" and hidden from the calendar. Usage can be from any user, regardless of who the reservation was originally created for. The cancellation process is triggered by a timed job on the web server.',
20+
null=True,
21+
),
22+
),
23+
migrations.AddField(
24+
model_name="tool",
25+
name="_late_cancellation_reservation_threshold",
26+
field=models.PositiveIntegerField(
27+
blank=True,
28+
db_column="late_cancellation_reservation_threshold",
29+
help_text='The amount of time (in minutes) before its start time that a tool reservation may be canceled before it is automatically marked as "missed" and hidden from the calendar.',
30+
null=True,
31+
),
32+
),
33+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 4.2.27 on 2026-01-22 21:08
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("NEMO", "0138_area_late_cancellation_reservation_threshold_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="UnplannedOutage",
16+
fields=[
17+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
18+
("start", models.DateTimeField()),
19+
("end", models.DateTimeField(blank=True, null=True)),
20+
("tool", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="NEMO.tool")),
21+
],
22+
options={
23+
"abstract": False,
24+
},
25+
),
26+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 4.2.27 on 2026-01-22 21:21
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("NEMO", "0139_unplannedoutage"),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name="user",
15+
options={
16+
"ordering": ["first_name"],
17+
"permissions": (
18+
("trigger_timed_services", "Can trigger timed services"),
19+
("use_billing_api", "Can use billing API"),
20+
("use_project_billing", "Can use project billing"),
21+
("kiosk", "Kiosk services"),
22+
("can_impersonate_users", "Can impersonate other users"),
23+
),
24+
},
25+
),
26+
]

0 commit comments

Comments
 (0)