Skip to content

Commit

Permalink
Merge pull request #131 from usnistgov/4.4.0.dev
Browse files Browse the repository at this point in the history
4.4.0.dev
  • Loading branch information
rptmat57 authored Jan 25, 2023
2 parents ba8ca5c + 06794d0 commit d9d0fc8
Show file tree
Hide file tree
Showing 56 changed files with 1,115 additions and 411 deletions.
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
FROM python:3.7
FROM python:3.8

RUN apt-get update && apt-get upgrade -y

# Intall NEMO (in the current directory) and Gunicorn
COPY . /nemo/
RUN pip install /nemo/ gunicorn==19.9.0
RUN pip install /nemo/ gunicorn==20.1.0
RUN rm --recursive --force /nemo/

RUN mkdir /nemo
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.splash_pad
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM python:3.7
FROM python:3.8

RUN apt-get update && apt-get install -y systemctl rsync
RUN apt-get update && apt-get upgrade -y && apt-get install -y systemctl rsync

COPY . /nemo/
RUN pip install /nemo/
Expand Down
6 changes: 5 additions & 1 deletion NEMO/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
def init_admin_site():
from NEMO.views.customization import ApplicationCustomization
from django.contrib import admin

# customize the site
site_title = ApplicationCustomization.get("site_title", raise_exception=False)
admin.site.site_header = site_title
Expand All @@ -15,16 +16,19 @@ def init_admin_site():

def init_rates():
from NEMO.rates import rate_class

rate_class.load_rates()


class NEMOConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
name = "NEMO"

def ready(self):
if 'migrate' in sys.argv or 'makemigrations' in sys.argv:
if "migrate" in sys.argv or "makemigrations" in sys.argv:
return
from django.apps import apps

if apps.is_installed("django.contrib.admin"):
init_admin_site()
init_rates()
Expand Down
25 changes: 24 additions & 1 deletion NEMO/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@
ReservationQuestions,
Resource,
ResourceCategory,
SafetyCategory,
SafetyIssue,
SafetyItem,
SafetyItemDocuments,
SafetyTraining,
ScheduledOutage,
ScheduledOutageCategory,
Expand Down Expand Up @@ -952,7 +955,6 @@ class UserAdmin(admin.ModelAdmin):
"badge_number",
"type",
"domain",
"discipline",
"notes"
)
},
Expand Down Expand Up @@ -1071,6 +1073,27 @@ class SafetyIssueAdmin(admin.ModelAdmin):
search_fields = ("location", "concern", "progress", "resolution")


@register(SafetyCategory)
class SafetyCategoryAdmin(admin.ModelAdmin):
list_display = ("name", "display_order")


class SafetyItemDocumentsInline(admin.TabularInline):
model = SafetyItemDocuments
extra = 1


@register(SafetyItem)
class SafetyItemAdmin(admin.ModelAdmin):
inlines = [SafetyItemDocumentsInline]
list_display = ("name", "category", "get_documents_number")
list_filter = ("category",)

@display(description='Documents')
def get_documents_number(self, obj: SafetyItem):
return SafetyItemDocuments.objects.filter(safety_item=obj).count()


@register(Door)
class DoorAdmin(admin.ModelAdmin):
list_display = ("name", "area", "interlock", "get_absolute_url", "id")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{% load custom_tags_and_filters %}
<h1>Welcome to the {{ area }}, {{ user.first_name }}!</h1>
<h2>Please choose a project to bill for area access.</h2>
<h2>You can always change your choice later.</h2>
{% for project in user.active_projects %}
<p>
<button type="button" class="btn btn-success btn-lg btn-block btn-extra-large" onclick="enter_area('{{ user.badge_number }}', {{ project.id }})">
{{ project.name }}
{% project_selection_display project %}
</button>
</p>
{% endfor %}
Expand Down
6 changes: 4 additions & 2 deletions NEMO/apps/kiosk/templates/kiosk/tool_information.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ <h2>
</h2>
{% endwith %}
</div>
{% if customer.is_staff and tool.in_use %}
<p>You may <a href="javascript:void(0)" onclick="disable_tool();">force {{ tool.get_current_usage_event.operator }} off this tool</a>.</p>
{% if tool.in_use %}
{% if customer.is_staff or customer.is_user_office %}
<p>You may <a href="javascript:void(0)" onclick="disable_tool();">force {{ tool.get_current_usage_event.operator }} off this tool</a>.</p>
{% endif %}
{% endif %}
{% elif tool.delayed_logoff_in_progress %}
{% with tool.get_delayed_logoff_usage_event as delayed_logoff_event %}
Expand Down
2 changes: 1 addition & 1 deletion NEMO/apps/sensors/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from pymodbus.client.sync import ModbusTcpClient
from pymodbus.client import ModbusTcpClient

from NEMO.apps.sensors.admin import SensorAdminForm, SensorCardAdminForm
from NEMO.apps.sensors.evaluators import evaluate_expression, evaluate_modbus_expression
Expand Down
12 changes: 11 additions & 1 deletion NEMO/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from NEMO.models import Area, Notification, PhysicalAccessLevel, Tool, User
from NEMO.utilities import date_input_js_format, datetime_input_js_format, time_input_js_format
from NEMO.views.customization import ApplicationCustomization, RecurringChargesCustomization
from NEMO.views.customization import ApplicationCustomization, RecurringChargesCustomization, SafetyCustomization
from NEMO.views.notifications import get_notification_counts


Expand Down Expand Up @@ -62,10 +62,18 @@ def base_context(request):
temporary_access_notification_count = notification_counts.get(Notification.Types.TEMPORARY_ACCESS_REQUEST, 0)
except:
temporary_access_notification_count = 0
try:
safety_notification_count = notification_counts.get(Notification.Types.SAFETY, 0)
except:
safety_notification_count = 0
try:
facility_managers_exist = User.objects.filter(is_active=True, is_facility_manager=True).exists()
except:
facility_managers_exist = False
try:
safety_menu_item = SafetyCustomization.get_bool("safety_main_menu")
except:
safety_menu_item = True
return {
"facility_name": facility_name,
"recurring_charges_name": recurring_charges_name,
Expand All @@ -78,9 +86,11 @@ def base_context(request):
"notification_counts": notification_counts,
"buddy_notification_count": buddy_notification_count,
"temporary_access_notification_count": temporary_access_notification_count,
"safety_notification_count": safety_notification_count,
"facility_managers_exist": facility_managers_exist,
"time_input_js_format": time_input_js_format,
"date_input_js_format": date_input_js_format,
"datetime_input_js_format": datetime_input_js_format,
"no_header": request.session.get("no_header", False),
"safety_menu_item": safety_menu_item,
}
1 change: 1 addition & 0 deletions NEMO/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def decorator(view_func=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url
accounting_or_user_office_or_manager_required = permission_decorator(lambda u: u.is_active and (u.is_accounting_officer or u.is_user_office or u.is_facility_manager or u.is_superuser))
user_office_or_facility_manager_required = permission_decorator(lambda u: u.is_active and (u.is_user_office or u.is_facility_manager or u.is_superuser))
any_staff_required = permission_decorator(lambda u: u.is_active and (u.is_any_part_of_staff))
accounting_or_manager_required = permission_decorator(lambda u: u.is_active and (u.is_accounting_officer or u.is_facility_manager or u.is_superuser))


# Use this decorator annotation to replace another existing function. The first parameter of
Expand Down
13 changes: 7 additions & 6 deletions NEMO/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@


class NEMOException(Exception):
""" Basic NEMO exception """
"""Basic NEMO exception"""

default_msg = "A {} error occurred"

def __init__(self, msg=None):
if msg is None:
from NEMO.views.customization import ApplicationCustomization
site_title = ApplicationCustomization.get('site_title')

site_title = ApplicationCustomization.get("site_title")
msg = self.default_msg.format(site_title)
self.msg = msg
super().__init__(msg)
Expand All @@ -26,7 +27,7 @@ def __init__(self, name: str, value: str = None):


class InterlockError(NEMOException):
""" Interlock related errors """
"""Interlock related errors"""

def __init__(self, interlock: Interlock, msg=None):
self.message = msg
Expand All @@ -38,7 +39,7 @@ def __init__(self, interlock: Interlock, msg=None):


class UserAccessError(NEMOException):
""" User access related errors """
"""User access related errors"""

detailed_msg = ""

Expand Down Expand Up @@ -128,9 +129,9 @@ def __init__(self, project, user, item_name, msg=None):


class RequiredUnansweredQuestionsException(NEMOException):
def __init__(self, run_data:str, questions: List):
def __init__(self, run_data: str, questions: List):
self.run_data = run_data
self.questions = questions
display_questions = ", ".join([f"\"{question.title}\"" for question in questions])
display_questions = ", ".join([f'"{question.title}"' for question in questions])
msg = f"You have to answer the following required questions: {display_questions}"
super().__init__(msg)
26 changes: 16 additions & 10 deletions NEMO/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
CharField,
ChoiceField,
DateField,
FileField,
Form,
ImageField,
IntegerField,
Expand All @@ -27,7 +26,6 @@
Consumable,
ConsumableWithdraw,
Project,
ProjectDocuments,
RecurringConsumableCharge,
ReservationItemType,
SafetyIssue,
Expand All @@ -38,7 +36,6 @@
TaskImages,
TemporaryPhysicalAccessRequest,
User,
UserDocuments,
UserPreferences,
)
from NEMO.utilities import bootstrap_primary_color, format_datetime, quiet_int
Expand All @@ -60,7 +57,7 @@ class Meta:
"date_joined",
"last_login",
"managed_projects",
"preferences"
"preferences",
]


Expand Down Expand Up @@ -288,7 +285,9 @@ def clean(self):
consumable = cleaned_data["consumable"]
if not consumable.reusable and quantity > consumable.quantity:
raise ValidationError(
'There are not enough "' + consumable.name + '". (The current quantity in stock is '
'There are not enough "'
+ consumable.name
+ '". (The current quantity in stock is '
+ str(consumable.quantity)
+ "). Please order more as soon as possible."
)
Expand Down Expand Up @@ -369,7 +368,9 @@ class EmailBroadcastForm(Form):
contents = CharField(required=False)
copy_me = BooleanField(required=False, initial=True)

audience = ChoiceField(choices=[("tool", "tool"), ("project", "project"), ("account", "account"), ("area", "area"), ("user", "user")])
audience = ChoiceField(
choices=[("tool", "tool"), ("project", "project"), ("account", "account"), ("area", "area"), ("user", "user")]
)
selection = CharField(required=False)
no_type = BooleanField(initial=False, required=False)
only_active_users = BooleanField(required=False, initial=True)
Expand All @@ -378,7 +379,7 @@ def clean_title(self):
return self.cleaned_data["title"].upper()

def clean_selection(self):
return self.data.getlist('selection')
return self.data.getlist("selection")


class AlertForm(ModelForm):
Expand Down Expand Up @@ -450,8 +451,11 @@ def clean(self):
cleaned_data = super().clean()
other_users = len(cleaned_data.get("other_users")) if "other_users" in cleaned_data else 0
minimum_total_users = quiet_int(UserRequestsCustomization.get("access_requests_minimum_users"), 2)
if other_users < minimum_total_users -1:
self.add_error("other_users", f"You need at least {minimum_total_users-1} other {'buddy' if minimum_total_users == 2 else 'buddies'} for this request")
if other_users < minimum_total_users - 1:
self.add_error(
"other_users",
f"You need at least {minimum_total_users - 1} other {'buddy' if minimum_total_users == 2 else 'buddies'} for this request",
)
return cleaned_data


Expand All @@ -463,7 +467,9 @@ class Meta:

def nice_errors(obj, non_field_msg="General form errors") -> ErrorDict:
result = ErrorDict()
error_dict = obj.errors if isinstance(obj, BaseForm) else obj.message_dict if isinstance(obj, ValidationError) else {}
error_dict = (
obj.errors if isinstance(obj, BaseForm) else obj.message_dict if isinstance(obj, ValidationError) else {}
)
for field_name, errors in error_dict.items():
if field_name == NON_FIELD_ERRORS:
key = non_field_msg
Expand Down
2 changes: 1 addition & 1 deletion NEMO/interlocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from pymodbus.client.sync import ModbusTcpClient
from pymodbus.client import ModbusTcpClient

from NEMO.admin import InterlockAdminForm, InterlockCardAdminForm
from NEMO.exceptions import InterlockError
Expand Down
2 changes: 1 addition & 1 deletion NEMO/migrations/0042_version_4_3_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,6 @@ def migrate_qualifications(apps, schema_editor):
migrations.AlterField(
model_name='user',
name='is_staff',
field=models.BooleanField(default=False, help_text='Designates this user as techinical staff. Technical staff can start remote projects, check maintenance, change configuration, train users etc.', verbose_name='staff'),
field=models.BooleanField(default=False, help_text='Designates this user as technical staff. Technical staff can start remote projects, check maintenance, change configuration, train users etc.', verbose_name='staff'),
),
]
Loading

0 comments on commit d9d0fc8

Please sign in to comment.