Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.2.0.dev #119

Merged
merged 52 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
553f62b
- starting 4.2.0 dev
rptmat57 Jun 15, 2022
f9eb636
Merge branch 'master' into 4.2.0.dev
rptmat57 Jun 20, 2022
34e9f50
- added ldap username format option
rptmat57 Jun 25, 2022
e919c0c
- added typehint
rptmat57 Jun 25, 2022
ab078bd
Merge branch 'master' into 4.2.0.dev
rptmat57 Jul 6, 2022
e53dda1
- updated django to 3.2.14 for vulnerability issue
rptmat57 Jul 6, 2022
537c3fa
- updated css for customization separation
rptmat57 Jul 30, 2022
06ed8b1
- drf-flex-fields 0.9.8 -> 0.9.9
rptmat57 Jul 30, 2022
e08890b
- fixed bug preventing rates from being loaded at startup
rptmat57 Jul 30, 2022
e2a55e1
- reverted to previous version of drf-flex-field since new one is not…
rptmat57 Jul 30, 2022
cef538a
- fixed rates customization throwing error on application initialisation
rptmat57 Jul 30, 2022
f6b79fb
- updated rate names for consistency with CNST page
rptmat57 Aug 2, 2022
03ed589
- fixed a bug when using login form and authorization fails because i…
rptmat57 Aug 6, 2022
195729c
- fixed interlocks not accepting 0 as coil/channel for Modbus or not …
rptmat57 Aug 9, 2022
71a0aa7
- updated django to 3.2.15 for vulnerability issue
rptmat57 Aug 11, 2022
0de421c
- made username case-insensitive for login and validation
rptmat57 Aug 15, 2022
8ca3c47
- fixed wrong imports
rptmat57 Aug 19, 2022
af71386
- fixing ast import
rptmat57 Aug 19, 2022
574a9e3
- fixing ast import
rptmat57 Aug 19, 2022
1a25466
- added staff absence note only visible to managers
rptmat57 Aug 22, 2022
dc93fde
- fixed issue with expanding/collapsing categories while switching be…
rptmat57 Aug 22, 2022
18458d8
- fixed wrong imports
rptmat57 Aug 22, 2022
4ce73b6
- add sidebar button to toggle tools user is qualified for
pdessauw Aug 22, 2022
d3baf2e
- add button to enable/disable show qualified tools button on Calendar
pdessauw Aug 22, 2022
0b35922
- added safety data sheets with chemicals and hazard categories
rptmat57 Aug 23, 2022
3cb2044
- fixed alignment in safety_data_sheets.html
rptmat57 Aug 23, 2022
30d5942
- fix API call not working for regular users
pdessauw Aug 23, 2022
a36b65a
- fixed billing data bug
rptmat57 Aug 24, 2022
749f341
- removed sds from navigation bar
rptmat57 Aug 24, 2022
54ed3be
- added static folder to gitignore
rptmat57 Aug 24, 2022
4a686c6
- reverted static folder
rptmat57 Aug 24, 2022
404d59c
- hiding keywords by default and making search take them into account…
rptmat57 Aug 24, 2022
b86c871
Merge pull request #117 from pdessauw/feat/tools-visibility-tooltip
rptmat57 Aug 24, 2022
ef3df91
- reformat
rptmat57 Aug 24, 2022
edb3d65
- consolidating 4.2.0 migrations
rptmat57 Aug 25, 2022
27492ad
- added link for SDS
rptmat57 Aug 29, 2022
914d45b
- fixed warning for ClosureTime
rptmat57 Aug 30, 2022
81efa5b
- fixed group reservation question not updating submit button on add/…
rptmat57 Aug 30, 2022
b405af1
- added hover in staff status
rptmat57 Aug 30, 2022
9cb1d13
- drf-flex-fields 0.9.8 -> 0.9.9
rptmat57 Aug 30, 2022
3f648cb
- using different icons for qualified tools
rptmat57 Aug 31, 2022
cb5af2d
- switching to a better sds icon
rptmat57 Aug 31, 2022
f90b69e
- updated default facility name
rptmat57 Sep 1, 2022
e34758d
- updated Customization spelling for consistency
rptmat57 Sep 1, 2022
b6be5b7
- fixed safety data sheet search width
rptmat57 Sep 1, 2022
e054b43
- made tool comment keep new lines
rptmat57 Sep 1, 2022
cfef86c
- added chemical hazard and chemical data in splash pad
rptmat57 Sep 1, 2022
312de94
- centering checkmarks in Safety data sheets
rptmat57 Sep 1, 2022
8428146
- fixed styling of table in safety data sheets for Firefox
rptmat57 Sep 1, 2022
6d588f5
- fixed tests after changing NanoFab to Facility
rptmat57 Sep 2, 2022
daecdd6
- cryptography 37.0.2 -> 37.0.4
rptmat57 Sep 2, 2022
c6c11dc
- preparing for 4.2.0 release
rptmat57 Sep 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions Dockerfile.splash_pad
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ RUN rm --recursive --force /nemo

RUN mkdir /nemo
RUN mkdir /nemo/media
RUN mkdir /nemo/media/tool_images
WORKDIR /nemo

COPY resources/icons/* /nemo/media/
COPY resources/people/* /nemo/media/
COPY resources/sounds/* /nemo/media/
COPY resources/images/tool_images/* /nemo/media/tool_images/
COPY resources/images/* /nemo/media/
COPY resources/emails/* /nemo/media/
COPY resources/icons/ /nemo/media
COPY resources/people/ /nemo/media/
COPY resources/sounds/ /nemo/media/
COPY resources/images/ /nemo/media/
COPY resources/emails/ /nemo/media/
COPY resources/splash_pad_rates.json /nemo/media/rates.json
COPY resources/splash_pad_settings.py /nemo/
COPY resources/fixtures/splash_pad.json /nemo/
Expand Down
2 changes: 1 addition & 1 deletion NEMO/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class NEMOConfig(AppConfig):
name = "NEMO"

def ready(self):
if 'migrate' 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"):
Expand Down
48 changes: 47 additions & 1 deletion NEMO/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
BadgeReader,
BuddyRequest,
BuddyRequestMessage,
Chemical,
ChemicalHazard,
Closure,
ClosureTime,
Comment,
Expand Down Expand Up @@ -155,7 +157,7 @@ def clean(self):
primary_owner = cleaned_data.get("_primary_owner")
image = cleaned_data.get("_image")

# only resize if an image is present and has changed
# only resize if an image is present and has changed
if image and not isinstance(image, FieldFile):
from NEMO.utilities import resize_image

Expand Down Expand Up @@ -1391,6 +1393,50 @@ class StaffAbsenceAdmin(admin.ModelAdmin):
list_filter = ("staff_member", "absence_type", "start_date", "end_date", "creation_time")


class ChemicalHazardAdminForm(forms.ModelForm):
class Meta:
model = ChemicalHazard
fields = "__all__"

chemicals = forms.ModelMultipleChoiceField(
queryset=Chemical.objects.all(),
required=False,
widget=FilteredSelectMultiple(verbose_name="Chemicals", is_stacked=False),
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.fields["chemicals"].initial = self.instance.chemical_set.all()

def clean(self):
cleaned_data = super().clean()
logo = cleaned_data.get("logo")

# only resize if a logo is present and has changed
if logo and not isinstance(logo, FieldFile):
from NEMO.utilities import resize_image

# resize image to 250x250 maximum
cleaned_data["logo"] = resize_image(logo, 250)


@register(ChemicalHazard)
class ChemicalHazardAdmin(admin.ModelAdmin):
form = ChemicalHazardAdminForm
list_display = ("name", "display_order")

def save_model(self, request, obj: ChemicalHazard, form, change):
super().save_model(request, obj, form, change)
if "chemicals" in form.changed_data:
obj.chemical_set.set(form.cleaned_data["chemicals"])


@register(Chemical)
class ChemicalAdmin(admin.ModelAdmin):
filter_horizontal = ("hazards",)


@register(EmailLog)
class EmailLogAdmin(admin.ModelAdmin):
list_display = ["id", "category", "sender", "to", "subject", "when", "ok"]
Expand Down
4 changes: 2 additions & 2 deletions NEMO/apps/kiosk/templates/kiosk/tool_information.html
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ <h2>This tool is <strong>operational</strong> and <strong>idle</strong>.</h2>
<div class="media">
<span class="glyphicon glyphicon-comment pull-left notification-icon primary-highlight"></span>
<div class="media-body">
<span class="media-middle">{{ c.content }}</span>
<span class="media-middle">{{ c.content|linebreaksbr }}</span>
<span class="media-bottom">{{ c.author }} wrote this comment on {{ c.creation_date }}</span>
</div>
</div>
Expand All @@ -188,7 +188,7 @@ <h2>This tool is <strong>operational</strong> and <strong>idle</strong>.</h2>
<div class="media">
<span class="glyphicon glyphicon-comment pull-left notification-icon primary-highlight"></span>
<div class="media-body">
<span class="media-middle">{{ c.content }}</span>
<span class="media-middle">{{ c.content|linebreaksbr }}</span>
<span class="media-bottom">{{ c.author }} wrote this comment on {{ c.creation_date }}</span>
</div>
</div>
Expand Down
25 changes: 12 additions & 13 deletions NEMO/apps/sensors/evaluators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ast
import operator
from _ast import BinOp, BoolOp, Call, Compare, Index, Name, NameConstant, Num, Slice, Subscript, UnaryOp
from math import ceil, floor, sqrt, trunc

from pymodbus.constants import Endian
Expand Down Expand Up @@ -38,27 +37,27 @@ class BasicEvaluatorVisitor(ast.NodeVisitor):
def __init__(self, **kwargs):
self._variables = kwargs

def visit_Name(self, node: Name):
def visit_Name(self, node: ast.Name):
if node.id in self._variables:
return self._variables[node.id]
else:
raise AttributeError(f"Variable not found: {node.id}")

def visit_Num(self, node: Num):
def visit_Num(self, node: ast.Num):
return node.n

def visit_NameConstant(self, node: NameConstant):
def visit_NameConstant(self, node: ast.NameConstant):
return node.value

def visit_UnaryOp(self, node: UnaryOp):
def visit_UnaryOp(self, node: ast.UnaryOp):
val = self.visit(node.operand)
op = type(node.op)
if op in self.operators:
return self.operators[op](val)
else:
raise TypeError(f"Unsupported operation: {op.__name__}")

def visit_BinOp(self, node: BinOp):
def visit_BinOp(self, node: ast.BinOp):
lhs = self.visit(node.left)
rhs = self.visit(node.right)
op = type(node.op)
Expand All @@ -67,19 +66,19 @@ def visit_BinOp(self, node: BinOp):
else:
raise TypeError(f"Unsupported operation: {op.__name__}")

def visit_Subscript(self, node: Subscript):
def visit_Subscript(self, node: ast.Subscript):
val = self.visit(node.value)
index = self.visit(node.slice)
try:
return val[index]
except AttributeError:
return self.generic_visit(node)

def visit_Index(self, node: Index, **kwargs):
def visit_Index(self, node: ast.Index, **kwargs):
"""df.index[4]"""
return self.visit(node.value)

def visit_Slice(self, node: Slice):
def visit_Slice(self, node: ast.Slice):
lower = node.lower
if lower is not None:
lower = self.visit(lower)
Expand All @@ -92,7 +91,7 @@ def visit_Slice(self, node: Slice):

return slice(lower, upper, step)

def visit_Call(self, node: Call):
def visit_Call(self, node: ast.Call):
if node.func.id in self.functions:
new_args = [self.visit(arg) for arg in node.args]
return self.functions[node.func.id](*new_args)
Expand Down Expand Up @@ -135,7 +134,7 @@ def modbus_function(registers):
# noinspection PyTypeChecker
class ModbusEvaluatorVisitor(BasicEvaluatorVisitor):
# Extension of the basic evaluator with additional modbus specific functions
def visit_Call(self, node: Call):
def visit_Call(self, node: ast.Call):
if node.func.id in self.functions:
return super().visit_Call(node)
elif node.func.id in modbus_functions:
Expand Down Expand Up @@ -164,14 +163,14 @@ class BooleanEvaluatorVisitor(BasicEvaluatorVisitor):
def visit_bool(self, node: bool):
return node

def visit_BoolOp(self, node: BoolOp):
def visit_BoolOp(self, node: ast.BoolOp):
if isinstance(node.op, (ast.And, ast.Or)):
values = map(self.visit, node.values)
return all(values) if isinstance(node.op, ast.And) else any(values)
else:
return self.generic_visit(self, node)

def visit_Compare(self, node: Compare, **kwargs):
def visit_Compare(self, node: ast.Compare, **kwargs):
# base case: we have something like a CMP b
if len(node.comparators) == 1:
bin_op = ast.BinOp(op=node.ops[0], left=node.left, right=node.comparators[0])
Expand Down
6 changes: 3 additions & 3 deletions NEMO/interlocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def setRelayState(cls, interlock: Interlock_model, state: {0, 1}) -> Interlock_m
if interlock.card.username and interlock.card.password:
auth = (interlock.card.username, interlock.card.password)
for state_xml_name in cls.state_xml_names:
url = f"{interlock.card.server}:{interlock.card.port}/{state_xml_name}?{cls.state_parameter_template.format(interlock.channel)}={state}"
url = f"{interlock.card.server}:{interlock.card.port}/{state_xml_name}?{cls.state_parameter_template.format(interlock.channel or '')}={state}"
if not url.startswith("http") and not url.startswith("https"):
url = "http://" + url
response = requests.get(url, auth=auth)
Expand All @@ -327,7 +327,7 @@ def setRelayState(cls, interlock: Interlock_model, state: {0, 1}) -> Interlock_m
state = None
# Try with a few different lookups here since depending on the relay model, it could be relayX or relayXstate
for state_suffix in cls.state_response_suffixes:
element = responseXML.find(cls.state_parameter_template.format(interlock.channel) + state_suffix)
element = responseXML.find(cls.state_parameter_template.format(interlock.channel or '') + state_suffix)
# Explicitly check for None since 0 is a valid state to return
if element is not None:
state = int(element.text)
Expand Down Expand Up @@ -360,7 +360,7 @@ class ModbusTcpInterlock(Interlock):
def clean_interlock(self, interlock_form: InterlockAdminForm):
channel = interlock_form.cleaned_data["channel"]
error = {}
if not channel:
if channel is None:
error["channel"] = _("This field is required.")
if error:
raise ValidationError(error)
Expand Down
4 changes: 2 additions & 2 deletions NEMO/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.conf import settings
from django.contrib.auth.middleware import RemoteUserMiddleware
from django.http import HttpResponseForbidden
from django.shortcuts import redirect
from django.urls import NoReverseMatch, reverse
from django.utils.deprecation import MiddlewareMixin

Expand Down Expand Up @@ -39,8 +40,7 @@ def process_request(self, request):
from NEMO.views.authentication import all_auth_backends_are_pre_auth
# Only raise error if all we have are pre_authentication backends and they failed
if all_auth_backends_are_pre_auth():
from NEMO.views.authentication import authorization_failed
return authorization_failed(request)
return redirect('authorization_failed')


class HTTPHeaderAuthenticationMiddleware(RemoteUserAuthenticationMiddleware):
Expand Down
56 changes: 56 additions & 0 deletions NEMO/migrations/0040_version_4_2_0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 3.2.13 on 2022-06-15 19:37

from django.db import migrations, models

import NEMO.utilities
from NEMO.migrations_utils import create_news_for_version


class Migration(migrations.Migration):

dependencies = [
('NEMO', '0039_version_4_1_0'),
]

def new_version_news(apps, schema_editor):
create_news_for_version(apps, "4.2.0", "")

operations = [
migrations.RunPython(new_version_news),
migrations.AddField(
model_name='staffabsence',
name='manager_note',
field=models.TextField(blank=True, help_text='A note only visible to managers.', null=True),
),
migrations.AlterField(
model_name='staffabsence',
name='description',
field=models.TextField(blank=True, help_text='The absence description. This will be visible to anyone.', null=True),
),
migrations.CreateModel(
name='ChemicalHazard',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('display_order', models.IntegerField(help_text="Chemical hazards are sorted according to display order. The lowest value category is displayed first in the 'Safety data sheet' page.")),
('logo', models.ImageField(blank=True, help_text='The logo for this hazard', upload_to=NEMO.utilities.get_hazard_logo_filename)),
],
options={
'ordering': ['display_order', 'name'],
},
),
migrations.CreateModel(
name='Chemical',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('document', models.FileField(blank=True, null=True, upload_to=NEMO.utilities.get_chemical_document_filename)),
('url', models.CharField(blank=True, max_length=200, null=True, verbose_name='URL')),
('keywords', models.TextField(blank=True, null=True)),
('hazards', models.ManyToManyField(blank=True, help_text='Select the hazards for this chemical.', to='NEMO.ChemicalHazard')),
],
options={
'ordering': ['name'],
},
),
]
Loading