Skip to content

Commit d3e879e

Browse files
authored
Merge pull request #303 from usnistgov/7.3.0.dev
7.3.0.dev
2 parents a5b2148 + 8f0543f commit d3e879e

File tree

184 files changed

+14765
-2382
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

184 files changed

+14765
-2382
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
repos:
22
- repo: https://github.com/psf/black-pre-commit-mirror
3-
rev: 25.1.0
3+
rev: 25.12.0
44
hooks:
55
- id: black
6-
language_version: python3.11
6+
language_version: python3.13
77

88
- repo: https://github.com/djlint/djLint
99
rev: v1.36.4

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11
1+
FROM python:3.13
22

33
RUN apt-get update && apt-get upgrade -y
44
RUN apt-get install -y less vim

Dockerfile.splash_pad

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11
1+
FROM python:3.13
22

33
RUN apt-get update && apt-get upgrade -y && apt-get install -y systemctl rsync vim less
44

NEMO/actions.py

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,35 @@
1+
import json
2+
from collections.abc import Mapping
3+
from typing import Sequence
4+
15
from django.contrib import admin, messages
26
from django.db.models import Max
37
from django.urls import reverse
48
from django.utils.safestring import mark_safe
59

610
from NEMO.mixins import BillableItemMixin
7-
from NEMO.models import Area, Configuration, Interlock, InterlockCard, Tool, User
11+
from NEMO.models import Area, Configuration, Interlock, InterlockCard, Tool, ToolUsageQuestions, User
812
from NEMO.typing import QuerySetType
9-
from NEMO.utilities import export_format_datetime, new_model_copy
13+
from NEMO.utilities import export_format_datetime, get_django_default_perm, new_model_copy
1014
from NEMO.views.access_requests import access_csv_export
1115
from NEMO.views.adjustment_requests import adjustments_csv_export
1216

1317

14-
@admin.action(description="Disable selected cards")
18+
@admin.action(description="Disable selected cards", permissions=["change"])
1519
def disable_selected_cards(model_admin, request, queryset: QuerySetType[InterlockCard]):
1620
for interlock_card in queryset:
1721
interlock_card.enabled = False
1822
interlock_card.save(update_fields=["enabled"])
1923

2024

21-
@admin.action(description="Enable selected cards")
25+
@admin.action(description="Enable selected cards", permissions=["change"])
2226
def enable_selected_cards(model_admin, request, queryset: QuerySetType[InterlockCard]):
2327
for interlock_card in queryset:
2428
interlock_card.enabled = True
2529
interlock_card.save(update_fields=["enabled"])
2630

2731

28-
@admin.action(description="Lock selected interlocks")
32+
@admin.action(description="Lock selected interlocks", permissions=["change"])
2933
def lock_selected_interlocks(model_admin, request, queryset):
3034
for interlock in queryset:
3135
try:
@@ -38,7 +42,7 @@ def lock_selected_interlocks(model_admin, request, queryset):
3842
messages.error(request, f"{interlock} could not be locked due to the following error: {str(error)}")
3943

4044

41-
@admin.action(description="Unlock selected interlocks")
45+
@admin.action(description="Unlock selected interlocks", permissions=["change"])
4246
def unlock_selected_interlocks(model_admin, request, queryset):
4347
for interlock in queryset:
4448
try:
@@ -51,7 +55,7 @@ def unlock_selected_interlocks(model_admin, request, queryset):
5155
messages.error(request, f"{interlock} could not be unlocked due to the following error: {str(error)}")
5256

5357

54-
@admin.action(description="Synchronize selected interlocks with tool usage")
58+
@admin.action(description="Synchronize selected interlocks with tool usage", permissions=["change"])
5559
def synchronize_with_tool_usage(model_admin, request, queryset):
5660
for interlock in queryset:
5761
# Ignore interlocks with no tool assigned, and ignore interlocks connected to doors
@@ -65,6 +69,8 @@ def synchronize_with_tool_usage(model_admin, request, queryset):
6569

6670
@admin.action(description="Create next interlock")
6771
def create_next_interlock(model_admin, request, queryset):
72+
if not has_perm(request, queryset, "add") or not has_perm(request, queryset, "change"):
73+
model_admin.message_user(request, "You do not have permission to run this action.", level=messages.ERROR)
6874
for interlock in queryset:
6975
new_interlock = Interlock()
7076
new_interlock.card = interlock.card
@@ -74,7 +80,7 @@ def create_next_interlock(model_admin, request, queryset):
7480
new_interlock.save()
7581

7682

77-
@admin.action(description="Generate CSV status report for selected interlocks")
83+
@admin.action(description="Generate CSV status report for selected interlocks", permissions=["view"])
7884
def csv_interlock_status_report(model_admin, request, queryset: QuerySetType[Interlock]):
7985
from NEMO.interlocks import get_interlock_report
8086

@@ -85,6 +91,8 @@ def csv_interlock_status_report(model_admin, request, queryset: QuerySetType[Int
8591

8692
@admin.action(description="Duplicate selected tool configuration")
8793
def duplicate_tool_configuration(model_admin, request, queryset):
94+
if not has_perm(request, queryset, "add") or not has_perm(request, queryset, "change"):
95+
model_admin.message_user(request, "You do not have permission to run this action.", level=messages.ERROR)
8896
for tool in queryset:
8997
original_name = tool.name
9098
new_name = "Copy of " + tool.name
@@ -135,28 +143,30 @@ def duplicate_tool_configuration(model_admin, request, queryset):
135143
)
136144

137145

138-
@admin.action(description="Rebuild area tree")
146+
@admin.action(description="Rebuild area tree", permissions=["change"])
139147
def rebuild_area_tree(model_admin, request, queryset):
140148
Area.objects.rebuild()
141149

142150

143-
@admin.action(description="Export selected adjustment requests in CSV")
151+
@admin.action(description="Export selected adjustment requests in CSV", permissions=["view"])
144152
def adjustment_requests_export_csv(modeladmin, request, queryset):
145153
return adjustments_csv_export(queryset.all())
146154

147155

148-
@admin.action(description="Mark selected adjustment requests as applied")
156+
@admin.action(description="Mark selected adjustment requests as applied", permissions=["change"])
149157
def adjustment_requests_mark_as_applied(modeladmin, request, queryset):
150158
return queryset.update(applied=True, applied_by=request.user)
151159

152160

153-
@admin.action(description="Export selected access requests in CSV")
161+
@admin.action(description="Export selected access requests in CSV", permissions=["view"])
154162
def access_requests_export_csv(modeladmin, request, queryset):
155163
return access_csv_export(queryset.all())
156164

157165

158166
@admin.action(description="Duplicate selected configuration")
159167
def duplicate_configuration(model_admin, request, queryset: QuerySetType[Configuration]):
168+
if not has_perm(request, queryset, "add") or not has_perm(request, queryset, "change"):
169+
model_admin.message_user(request, "You do not have permission to run this action.", level=messages.ERROR)
160170
for configuration in queryset:
161171
original_name = configuration.name
162172
new_name = "Copy of " + configuration.name
@@ -188,8 +198,58 @@ def duplicate_configuration(model_admin, request, queryset: QuerySetType[Configu
188198
)
189199

190200

191-
@admin.action(description="Waive selected charges")
201+
@admin.action(description="Duplicate selected tool usage questions")
202+
def duplicate_tool_usage_questions(model_admin, request, queryset: QuerySetType[ToolUsageQuestions]):
203+
if not has_perm(request, queryset, "add") or not has_perm(request, queryset, "change"):
204+
model_admin.message_user(request, "You do not have permission to run this action.", level=messages.ERROR)
205+
for tool_usage_question in queryset:
206+
try:
207+
old_tools = tool_usage_question.only_for_tools.all()
208+
old_projects = tool_usage_question.only_for_projects.all()
209+
old_users = tool_usage_question.only_for_users.all()
210+
old_groups = tool_usage_question.only_for_groups.all()
211+
new_tool_usage_question = new_model_copy(tool_usage_question)
212+
new_tool_usage_question.display_order = tool_usage_question.display_order + 1
213+
214+
def walk(x):
215+
if isinstance(x, Mapping):
216+
x = dict(x)
217+
if "name" in x and isinstance(x["name"], str):
218+
x["name"] += f"_{new_tool_usage_question.display_order}"
219+
for k, v in x.items():
220+
x[k] = walk(v)
221+
return x
222+
elif isinstance(x, Sequence) and not isinstance(x, str):
223+
return [walk(i) for i in x]
224+
return x
225+
226+
new_tool_usage_question.questions = json.dumps(walk(json.loads(tool_usage_question.questions)), indent=4)
227+
228+
new_tool_usage_question.full_clean()
229+
new_tool_usage_question.save()
230+
new_tool_usage_question.only_for_tools.set(old_tools)
231+
new_tool_usage_question.only_for_projects.set(old_projects)
232+
new_tool_usage_question.only_for_users.set(old_users)
233+
new_tool_usage_question.only_for_groups.set(old_groups)
234+
messages.success(
235+
request,
236+
mark_safe(
237+
f'A duplicate of {str(tool_usage_question)} has been made as <a href="{reverse("admin:NEMO_toolusagequestions_change", args=[new_tool_usage_question.id])}">{str(new_tool_usage_question)}</a>'
238+
),
239+
)
240+
except Exception as error:
241+
messages.error(
242+
request,
243+
f"{str(tool_usage_question)} could not be duplicated because of the following error: {str(error)}",
244+
)
245+
246+
247+
@admin.action(description="Waive selected charges", permissions=["change"])
192248
def waive_selected_charges(model_admin, request, queryset: QuerySetType[BillableItemMixin]):
193249
for charge in queryset:
194250
charge.waive(request.user)
195251
messages.success(request, f"{model_admin.model.__name__} #{charge.id} has been successfully waived")
252+
253+
254+
def has_perm(request, qs, action) -> bool:
255+
return request.user.has_perm(get_django_default_perm(qs.model, action))

0 commit comments

Comments
 (0)