diff --git a/RIGS/forms.py b/RIGS/forms.py index 934ef76c..54604d5d 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -153,3 +153,15 @@ class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm): class EventAuthorisationRequestForm(forms.Form): email = forms.EmailField(required=True, label='Authoriser Email') + + +class EventRiskAssessmentForm(forms.ModelForm): + forms.BooleanField.widget = forms.RadioSelect(choices=[ + (True, 'Yes'), + (False, 'No') + ], attrs={'class':'custom-control-input'}) + + class Meta: + model = models.RiskAssessment + fields = '__all__' + exclude = ['event', 'completed_by'] diff --git a/RIGS/migrations/0041_auto_20200528_1751.py b/RIGS/migrations/0041_auto_20200528_1751.py new file mode 100644 index 00000000..7b60d9f7 --- /dev/null +++ b/RIGS/migrations/0041_auto_20200528_1751.py @@ -0,0 +1,57 @@ +# Generated by Django 3.0.3 on 2020-05-28 16:51 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0040_delete_rigsversion'), + ] + + operations = [ + migrations.RemoveField( + model_name='event', + name='risk_assessment_edit_url', + ), + migrations.CreateModel( + name='RiskAssessment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(blank=True, null=True)), + ('last_edited', models.DateTimeField(blank=True, null=True)), + ('nonstandard_equipment', models.BooleanField(default=False)), + ('nonstandard_use', models.BooleanField(default=False)), + ('contractors', models.BooleanField(default=False)), + ('other_companies', models.BooleanField(default=False)), + ('crew_fatigue', models.BooleanField(default=False)), + ('general_notes', models.TextField(blank=True, null=True)), + ('big_power', models.BooleanField(default=False)), + ('generators', models.BooleanField(default=False)), + ('other_companies_power', models.BooleanField(default=False)), + ('nonstandard_equipment_power', models.BooleanField(default=False)), + ('multiple_electrical_environments', models.BooleanField(default=False)), + ('power_notes', models.TextField(blank=True, null=True)), + ('noise_monitoring', models.BooleanField(default=False)), + ('sound_notes', models.TextField(blank=True, null=True)), + ('known_venue', models.BooleanField(default=False)), + ('safe_loading', models.BooleanField(default=False)), + ('safe_storage', models.BooleanField(default=False)), + ('area_outside_of_control', models.BooleanField(default=False)), + ('barrier_required', models.BooleanField(default=False)), + ('nonstandard_emergency_procedure', models.BooleanField(default=False)), + ('special_structures', models.BooleanField(default=False)), + ('persons_responsible_structures', models.TextField(blank=True, null=True)), + ('suspended_structures', models.BooleanField(default=False)), + ('completed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='completer', to=settings.AUTH_USER_MODEL, verbose_name='Completed By')), + ('power_mic', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='power_mic', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC')), + ], + ), + migrations.AddField( + model_name='event', + name='risk_assessment', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='RIGS.RiskAssessment'), + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 7cb34123..61d31fef 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -327,7 +327,7 @@ class Event(models.Model, RevisionMixin): auth_request_to = models.EmailField(null=True, blank=True) # Risk assessment info - risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True) + risk_assessment = models.ForeignKey('RiskAssessment', null=True, blank=True, on_delete=models.CASCADE) # Calculated values """ @@ -570,3 +570,48 @@ class Payment(models.Model): def __str__(self): return "%s: %d" % (self.get_method_display(), self.amount) + + +@reversion.register +class RiskAssessment(models.Model): + completed_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='completer', blank=True, null=True, + verbose_name="Completed By", on_delete=models.CASCADE) + created = models.DateTimeField(blank=True, null=True) + last_edited = models.DateTimeField(blank=True, null=True) + + # General + nonstandard_equipment = models.BooleanField(default=False) + nonstandard_use = models.BooleanField(default=False) + contractors = models.BooleanField(default=False) + other_companies = models.BooleanField(default=False) + crew_fatigue = models.BooleanField(default=False) + general_notes = models.TextField(blank=True, null=True) + + # Power + big_power = models.BooleanField(default=False) + power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True, + verbose_name="Power MIC", on_delete=models.CASCADE) + generators = models.BooleanField(default=False) + other_companies_power = models.BooleanField(default=False) + nonstandard_equipment_power = models.BooleanField(default=False) + multiple_electrical_environments = models.BooleanField(default=False) + power_notes = models.TextField(blank=True, null=True) + + # Sound + noise_monitoring = models.BooleanField(default=False) + sound_notes = models.TextField(blank=True, null=True) + + # Site + known_venue = models.BooleanField(default=False) + safe_loading = models.BooleanField(default=False) + safe_storage = models.BooleanField(default=False) + area_outside_of_control = models.BooleanField(default=False) + barrier_required = models.BooleanField(default=False) + nonstandard_emergency_procedure = models.BooleanField(default=False) + + # Structures + special_structures = models.BooleanField(default=False) + persons_responsible_structures = models.TextField(blank=True, null=True) + suspended_structures = models.BooleanField(default=False) + + # Blimey that was a lot of options diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index d4474e55..17162fc5 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -8,6 +8,7 @@ from django.core.mail import EmailMessage, EmailMultiAlternatives from django.views import generic from django.urls import reverse_lazy from django.shortcuts import get_object_or_404 +from django.http import HttpResponseRedirect from django.template import RequestContext from django.template.loader import get_template from django.conf import settings @@ -17,6 +18,7 @@ from django.http import HttpResponse from django.core.exceptions import SuspiciousOperation from django.db.models import Q from django.contrib import messages +from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from z3c.rml import rml2pdf @@ -82,25 +84,33 @@ class EventEmbed(EventDetail): template_name = 'event_embed.html' -class EventRA(generic.base.RedirectView): - permanent = False +class EventRADetail(generic.DetailView): + model = models.RiskAssessment + template_name = 'risk_assessment_detail.html' - def get_redirect_url(self, *args, **kwargs): - event = get_object_or_404(models.Event, pk=kwargs['pk']) - - if event.risk_assessment_edit_url: - return event.risk_assessment_edit_url - - params = { - 'entry.708610078': f'N{event.pk:05}', - 'entry.905899507': event.name, - 'entry.139491562': event.venue.name if event.venue else '', - 'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ( - (' - ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''), - 'entry.902421165': event.mic.name if event.mic else '' - } - return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params) + def get_object(self, queryset=None): + epk = self.kwargs.get(self.pk_url_kwarg) + event = models.Event.objects.get(pk=epk) + ra, created = models.RiskAssessment.objects.get_or_create(event=event) + ra.event = event + + if created: + ra.created = timezone.now() + + return ra + +class EventRAEdit(generic.UpdateView): + model = models.RiskAssessment + template_name = 'risk_assessment_form.html' + form_class = forms.EventRiskAssessmentForm + + def get_success_url(self): + ra = self.get_object() + ra.completed_by = self.request.user + ra.last_edited = timezone.now() + ra.save() + return super().get_success_url() class EventCreate(generic.CreateView): model = models.Event @@ -437,27 +447,3 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView): }) context['to_name'] = self.request.GET.get('to_name', None) return context - - -@method_decorator(csrf_exempt, name='dispatch') -class LogRiskAssessment(generic.View): - http_method_names = ["post"] - - def post(self, request, **kwargs): - data = request.POST - shared_secret = data.get("secret") - edit_url = data.get("editUrl") - rig_number = data.get("rigNum") - if shared_secret is None or edit_url is None or rig_number is None: - return HttpResponse(status=422) - - if shared_secret != settings.RISK_ASSESSMENT_SECRET: - return HttpResponse(status=403) - - rig_number = int(re.sub("[^0-9]", "", rig_number)) - - event = get_object_or_404(models.Event, pk=rig_number) - event.risk_assessment_edit_url = edit_url - event.save() - - return HttpResponse(status=200) diff --git a/RIGS/templates/partials/yes_no_radio.html b/RIGS/templates/partials/yes_no_radio.html new file mode 100644 index 00000000..a4dacf70 --- /dev/null +++ b/RIGS/templates/partials/yes_no_radio.html @@ -0,0 +1,10 @@ + +
+ {% for radio in formitem %} +
+ {{ radio.tag }} + +
+ {% endfor %} +
diff --git a/RIGS/templates/risk_assessment_detail.html b/RIGS/templates/risk_assessment_detail.html new file mode 100644 index 00000000..bd80d6dc --- /dev/null +++ b/RIGS/templates/risk_assessment_detail.html @@ -0,0 +1,31 @@ +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} +{% block title %}Risk Assessment for Event N{{ object.event.pk|stringformat:"05d" }} {{ object.event.name }}{% endblock %} + +{% block content %} +
+
+

Risk Assessment for Event N{{ object.event.pk|stringformat:"05d" }} {{ object.event.name }}

+
+
+
Meta Details
+
+
+
Created
+
+ {{ object.created }} +
+
By
+
+ {{ object.completed_by.name }} +
+
Last Edited
+
+ {{ object.last_edited }} +
+
+
+
+
+
+
+{% endblock %} diff --git a/RIGS/templates/risk_assessment_form.html b/RIGS/templates/risk_assessment_form.html new file mode 100644 index 00000000..a9aadaee --- /dev/null +++ b/RIGS/templates/risk_assessment_form.html @@ -0,0 +1,131 @@ +{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %} +{% load widget_tweaks %} +{% load static %} + +{% block title %}{% if object.pk %}Edit{% else %}Create{% endif %} Risk Assessment for Event N{{ event.pk|stringformat:"05d" }}{% endblock %} + +{% block css %} + {{ block.super }} + + +{% endblock %} + +{% block preload_js %} + {{ block.super }} + + +{% endblock %} + +{% block js %} + {{ block.super }} + + + + + + +{% endblock %} + +{% block content %} +
+

{% if object.pk %}Edit{% else %}Create{% endif %} Risk Assessment for Event N{{ event.pk|stringformat:"05d" }}

+
+ {% csrf_token %} +
+
+
+
General
+
+

If the answer is yes to any of the below, you must consult a supervisor and make sure all activities planned to be undertaken are sufficiently covered by health and safety documentation

+ {% include 'partials/yes_no_radio.html' with formitem=form.nonstandard_equipment label="Does the event require any hired in equipment or use of equipment that is not covered by TEC's standard risk assessments and method statements?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.nonstandard_use label="Are TEC using their equipment in a way that is abnormal?" note="i.e. Not covered by TECs standard health and safety documentation" %} + {% include 'partials/yes_no_radio.html' with formitem=form.contractors label="Are you using any external contractors?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.other_companies label="Are TEC working with any other companies on site?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.crew_fatigue label="Is crew fatigue likely to be a risk at any point during this event?" %} + + + {% render_field form.general_notes class+="form-control" %} +
+
+
+
+
+
+
+
Power
+
+ {% include 'partials/yes_no_radio.html' with formitem=form.big_power label="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?" %} + +
+ +
+

If the answer is yes to any of the below questions you must consult a power supervisor

+ {% include 'partials/yes_no_radio.html' with formitem=form.generators label="Will generators be used?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.other_companies_power label="Will TEC be supplying power to any other companies?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.nonstandard_equipment_power label="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.multiple_electrical_environments label="Will the electrical installation occupy more than one electrical environment?" %} + + {% render_field form.power_notes class+="form-control" %} +
+
+
+
+
+
+
+
Sound
+
+

If yes, consult a supervisor of sound and make sure an appropriate noise monitoring plan and risk assessment is drawn up

+ {% include 'partials/yes_no_radio.html' with formitem=form.noise_monitoring label="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?" %} + + {% render_field form.sound_notes class+="form-control" %} +
+
+
+
+
+
+
+
Site Details
+
+

If no to any of the below, a site visit normally should be conducted and an assessment of the venue completed and added to TEC's records

+ {% include 'partials/yes_no_radio.html' with formitem=form.known_venue label="Is the event in a venue that you and/or TEC have experience working in?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.safe_loading label="Is there a safe load in/out?" note="e.g. sufficient lighting, flat, not in a crowded area etc." %} + {% include 'partials/yes_no_radio.html' with formitem=form.safe_storage label="Are there areas to safely store equipment?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.area_outside_of_control label="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.barrier_required label="Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?" %} + {% include 'partials/yes_no_radio.html' with formitem=form.nonstandard_emergency_procedure label="Does the emergency procedure for the event differ from TEC's standard procedures?" %} +
+
+
+
+
+
+
+
Structures
+
+ {% include 'partials/yes_no_radio.html' with formitem=form.special_structures label="Does the event require use of winch stands, motors, MPT Towers, or staging?" %} + + {% render_field form.persons_responsible_structures class+="form-control mb-3" %} + {% include 'partials/yes_no_radio.html' with formitem=form.suspended_structures label="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?" %} +
+
+
+
+
+
+
+ +
+
+
+
+
+{% endblock %} diff --git a/RIGS/urls.py b/RIGS/urls.py index 3976ddca..d71a62ce 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -84,8 +84,10 @@ urlpatterns = [ name='event_oembed'), path('event//print/', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), name='event_print'), - path('event//ra/', permission_required_with_403('RIGS.change_event')(rigboard.EventRA.as_view()), + path('event//ra/', permission_required_with_403('RIGS.change_event')(rigboard.EventRADetail.as_view()), name='event_ra'), + path('event//ra/edit/', permission_required_with_403('RIGS.change_event')(rigboard.EventRAEdit.as_view()), + name='event_ra_edit'), path('event//edit/', permission_required_with_403('RIGS.change_event')(rigboard.EventUpdate.as_view()), name='event_update'), path('event//duplicate/', permission_required_with_403('RIGS.add_event')(rigboard.EventDuplicate.as_view()), @@ -137,8 +139,6 @@ urlpatterns = [ url(r'^ical/(?P\d+)/(?P\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"), - # Risk assessment API - path('log_risk_assessment/', rigboard.LogRiskAssessment.as_view(), name='log_risk_assessment'), # Legacy URLs path('rig/show//', RedirectView.as_view(permanent=True, pattern_name='event_detail')),