Risk assessment form now validates power training status where required

This commit is contained in:
2022-01-12 19:09:20 +00:00
parent 9c8d8f077d
commit b4c5493705
7 changed files with 53 additions and 61 deletions

23
Pipfile.lock generated
View File

@@ -309,10 +309,11 @@
}, },
"django-hcaptcha": { "django-hcaptcha": {
"hashes": [ "hashes": [
"sha256:2b80197c07bb8444249bcce3758b0472d369cca309fb02d7abcd0a856431b76b" "sha256:18804fb38a01827b6c65d111bac31265c1b96fcf52d7a54c3e2d2cb1c62ddcde",
"sha256:b2519eaf0cc97865ac72f825301122c5cf61e1e4852d6895994160222acb6c1a"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.1.0" "version": "==0.2.0"
}, },
"django-htmlmin": { "django-htmlmin": {
"hashes": [ "hashes": [
@@ -362,11 +363,11 @@
}, },
"django-widget-tweaks": { "django-widget-tweaks": {
"hashes": [ "hashes": [
"sha256:19bcb66a4a9e68493ced04e7124882d753c5be517ed001556f9e35a40147f545", "sha256:01b911a1b47629de0a3a3097774798dee4eb703b94d41666929f688d5f77c723",
"sha256:d6c64fbf92cd2df9031f597c1374982233c05a1190d295c39d1c57ce007569c7" "sha256:07674e32031eda81077f0b8e390ce78d1d415e700cedd0396ef0ce452e95b94d"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.4.9" "version": "==1.4.11"
}, },
"envparse": { "envparse": {
"hashes": [ "hashes": [
@@ -861,11 +862,11 @@
}, },
"sentry-sdk": { "sentry-sdk": {
"hashes": [ "hashes": [
"sha256:2a1757d6611e4bec7d672c2b7ef45afef79fed201d064f53994753303944f5a8", "sha256:2cec50166bcb67e1965f8073541b2321e3864cd6fd42a526bcde9f0c4e4cc3f8",
"sha256:e4cb107e305b2c1b919414775fa73a9997f996447417d22b98e7610ded1e9eb5" "sha256:7bbaa32bba806ec629962f207b597e86831c7ee2c1f287c21ba7de7fea9a9c46"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.5.1" "version": "==1.5.2"
}, },
"simplejson": { "simplejson": {
"hashes": [ "hashes": [
@@ -1538,11 +1539,11 @@
}, },
"pytest-reverse": { "pytest-reverse": {
"hashes": [ "hashes": [
"sha256:9f2a3b163378922dd332ed056a58af4cfd1ccc8ad4a76606f43ed43cfff2140b", "sha256:1695b7c9e51b28db5af13d579b33b54a80958d86b886dfabd2a246bcad3e082e",
"sha256:d878e28c785fb20291580aa816d566a21beac508e06a2c9eb4f934d49c31ce5c" "sha256:6acfb50acd11caf3d222366f5e1458dea2351d47b6ca5b08ab408158636250ba"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.0" "version": "==1.4.0"
}, },
"pytest-splinter": { "pytest-splinter": {
"hashes": [ "hashes": [

View File

@@ -8,6 +8,7 @@ from django.utils import timezone
from reversion import revisions as reversion from reversion import revisions as reversion
from RIGS import models from RIGS import models
from training.models import TrainingLevel
# Override the django form defaults to use the HTML date/time/datetime UI elements # Override the django form defaults to use the HTML date/time/datetime UI elements
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'}) forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
@@ -164,6 +165,9 @@ class EventRiskAssessmentForm(forms.ModelForm):
], attrs={'class': 'custom-control-input', 'required': 'true'}) ], attrs={'class': 'custom-control-input', 'required': 'true'})
def clean(self): def clean(self):
if self.cleaned_data.get('big_power'):
if not self.cleaned_data.get('power_mic').level_qualifications.filter(level__department=TrainingLevel.POWER).exists():
self.add_error('power_mic', forms.ValidationError("Your Power MIC must be a Power Technician.", code="power_tech_required"))
# Check expected values # Check expected values
unexpected_values = [] unexpected_values = []
for field, value in models.RiskAssessment.expected_values.items(): for field, value in models.RiskAssessment.expected_values.items():

View File

@@ -99,7 +99,7 @@ class RevisionMixin(object):
version = self.current_version version = self.current_version
if version is None: if version is None:
return None return None
return "V{0} | R{1}".format(version.pk, version.revision.pk) return f"V{version.pk} | R{version.revision.pk}"
class Person(models.Model, RevisionMixin): class Person(models.Model, RevisionMixin):
@@ -207,7 +207,7 @@ class VatRate(models.Model, RevisionMixin):
get_latest_by = 'start_at' get_latest_by = 'start_at'
def __str__(self): def __str__(self):
return self.comment + " " + str(self.start_at) + " @ " + str(self.as_percent) + "%" return f"{self.comment} {self.start_at} @ {self.as_percent}%"
class Venue(models.Model, RevisionMixin): class Venue(models.Model, RevisionMixin):
@@ -347,10 +347,10 @@ class Event(models.Model, RevisionMixin):
if self.pk: if self.pk:
if self.is_rig: if self.is_rig:
return str("N%05d" % self.pk) return str("N%05d" % self.pk)
else:
return self.pk return self.pk
else:
return "????" return "????"
# Calculated values # Calculated values
""" """
@@ -475,7 +475,7 @@ class Event(models.Model, RevisionMixin):
return reverse('event_detail', kwargs={'pk': self.pk}) return reverse('event_detail', kwargs={'pk': self.pk})
def __str__(self): def __str__(self):
return "{}: {}".format(self.display_id, self.name) return f"{self.display_id}: {self.name}"
def clean(self): def clean(self):
errdict = {} errdict = {}
@@ -521,11 +521,11 @@ class EventItem(models.Model, RevisionMixin):
ordering = ['order'] ordering = ['order']
def __str__(self): def __str__(self):
return "{}.{}: {} | {}".format(self.event_id, self.order, self.event.name, self.name) return f"{self.event_id}.{self.order}: {self.event.name} | {self.name}"
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return str("item {}".format(self.name)) return f"item {self.name}"
@reversion.register @reversion.register
@@ -543,7 +543,7 @@ class EventAuthorisation(models.Model, RevisionMixin):
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials) return f"{self.event.display_id} (requested by {self.sent_by.initials})"
class InvoiceManager(models.Manager): class InvoiceManager(models.Manager):
@@ -671,7 +671,6 @@ class RiskAssessment(models.Model, RevisionMixin):
# Power # Power
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?") big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
# If yes to the above two, you must answer...
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True, power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)") verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
outside = models.BooleanField(help_text="Is the event outdoors?") outside = models.BooleanField(help_text="Is the event outdoors?")

View File

@@ -2,9 +2,7 @@ from django.conf import settings
import django import django
import pytest import pytest
from django.core.management import call_command from django.core.management import call_command
from RIGS.models import VatRate, Profile from RIGS.models import VatRate
import random
from django.db import connection
from PyRIGS.tests import pages from PyRIGS.tests import pages
import os import os
from selenium import webdriver from selenium import webdriver

View File

@@ -1,15 +1,8 @@
from django import forms from django import forms
from datetime import date
from training import models from training import models
from RIGS.models import Profile from RIGS.models import Profile
class SessionLogForm(forms.Form):
pass
class QualificationForm(forms.ModelForm): class QualificationForm(forms.ModelForm):
class Meta: class Meta:
model = models.TrainingItemQualification model = models.TrainingItemQualification

View File

@@ -1,10 +1,8 @@
from django.db import models
from RIGS.models import RevisionMixin, Profile from RIGS.models import RevisionMixin, Profile
from reversion import revisions as reversion from reversion import revisions as reversion
from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.safestring import SafeData, mark_safe
@reversion.register(for_concrete_model=False, fields=[], follow=["qualifications_obtained", "level_qualifications"]) @reversion.register(for_concrete_model=False, fields=[], follow=["qualifications_obtained", "level_qualifications"])
@@ -105,13 +103,13 @@ class TrainingItemQualification(models.Model, RevisionMixin):
return str("{} in {}".format(self.get_depth_display(), self.item)) return str("{} in {}".format(self.get_depth_display(), self.item))
@classmethod @classmethod
def get_colour_from_depth(obj, depth): def get_colour_from_depth(cls, obj, depth):
if depth == 0: if depth == 0:
return "warning" return "warning"
elif depth == 1: if depth == 1:
return "success" return "success"
else:
return "info" return "info"
def get_absolute_url(self): def get_absolute_url(self):
return reverse('trainee_item_detail', kwargs={'pk': self.trainee.pk}) return reverse('trainee_item_detail', kwargs={'pk': self.trainee.pk})
@@ -157,16 +155,16 @@ class TrainingLevel(models.Model, RevisionMixin):
def department_colour(self): def department_colour(self):
if self.department == self.SOUND: if self.department == self.SOUND:
return "info" return "info"
elif self.department == self.LIGHTING: if self.department == self.LIGHTING:
return "dark" return "dark"
elif self.department == self.POWER: if self.department == self.POWER:
return "danger" return "danger"
elif self.department == self.RIGGING: if self.department == self.RIGGING:
return "warning" return "warning"
elif self.department == self.HAULAGE: if self.department == self.HAULAGE:
return "light" return "light"
else:
return "primary" return "primary"
def get_requirements_of_depth(self, depth): def get_requirements_of_depth(self, depth):
return self.requirements.filter(depth=depth) return self.requirements.filter(depth=depth)
@@ -197,8 +195,8 @@ class TrainingLevel(models.Model, RevisionMixin):
if len(needed_qualifications) > 0: if len(needed_qualifications) > 0:
return int(relavant_qualifications / float(len(needed_qualifications)) * 100) return int(relavant_qualifications / float(len(needed_qualifications)) * 100)
else:
return 0 return 0
def user_has_requirements(self, user): def user_has_requirements(self, user):
has_required_items = all(TrainingItem.user_has_qualification(req.item, user, req.depth) for req in self.requirements.all()) has_required_items = all(TrainingItem.user_has_qualification(req.item, user, req.depth) for req in self.requirements.all())
@@ -225,7 +223,7 @@ class TrainingLevel(models.Model, RevisionMixin):
@property @property
def get_icon(self): def get_icon(self):
if self.icon is not None: if self.icon is not None:
icon = "<span class='fas fa-{}'></span>".format(self.icon) icon = f"<span class='fas fa-{self.icon}'></span>"
else: else:
icon = "".join([w[0] for w in str(self).split()]) icon = "".join([w[0] for w in str(self).split()])
return mark_safe("<span class='badge badge-{} badge-pill' data-toggle='tooltip' title='{}'>{}</span>".format(self.department_colour, str(self), icon)) return mark_safe("<span class='badge badge-{} badge-pill' data-toggle='tooltip' title='{}'>{}</span>".format(self.department_colour, str(self), icon))
@@ -260,14 +258,14 @@ class TrainingLevelQualification(models.Model, RevisionMixin):
return self.level.get_icon return self.level.get_icon
def clean(self): def clean(self):
if level.level >= TrainingLevel.SUPERVISOR and level.department != TrainingLevel.HAULAGE: if self.level.level >= TrainingLevel.SUPERVISOR and self.level.department != TrainingLevel.HAULAGE:
trainee.is_supervisor = True self.trainee.is_supervisor = True
trainee.save() self.trainee.save()
def __str__(self): def __str__(self):
if self.level.is_common_competencies: if self.level.is_common_competencies:
return "{} is qualified in the {}".format(self.trainee, self.level) return f"{self.trainee} is qualified in the {self.level}"
return "{} is qualified as a {}".format(self.trainee, self.level) return f"{self.trainee} is qualified as a {self.level}"
class Meta: class Meta:
unique_together = ["trainee", "level"] unique_together = ["trainee", "level"]

View File

@@ -1,14 +1,13 @@
import reversion import reversion
from django.shortcuts import render
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views import generic from django.views import generic
from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin
from training import models, forms
from django.utils import timezone from django.utils import timezone
from django.db import transaction from django.db import transaction
from django.db.models import Q, Count, OuterRef, F, Subquery, Window from django.db.models import Q, Count
from PyRIGS.views import is_ajax, ModalURLMixin
from training import models, forms
from users import views from users import views
@@ -98,12 +97,12 @@ class TraineeList(generic.ListView):
def get_queryset(self): def get_queryset(self):
q = self.request.GET.get('q', "") q = self.request.GET.get('q', "")
filter = Q(first_name__icontains=q) | Q(last_name__icontains=q) | Q(initials__icontains=q) fil = Q(first_name__icontains=q) | Q(last_name__icontains=q) | Q(initials__icontains=q)
# try and parse an int # try and parse an int
try: try:
val = int(q) val = int(q)
filter = filter | Q(pk=val) fil = fil | Q(pk=val)
except: # noqa except: # noqa
# not an integer # not an integer
pass pass
@@ -200,7 +199,7 @@ class RemoveRequirement(generic.DeleteView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["page_title"] = "Delete Requirement '{}' from Training Level {}?".format(self.object, self.object.level) context["page_title"] = f"Delete Requirement '{self.object}' from Training Level {self.object.level}?"
return context return context
def get_success_url(self): def get_success_url(self):