Compare commits

..

8 Commits

10 changed files with 136 additions and 75 deletions

View File

@@ -20,6 +20,7 @@ admin.site.register(models.VatRate, VersionAdmin)
admin.site.register(models.Event, VersionAdmin)
admin.site.register(models.EventItem, VersionAdmin)
admin.site.register(models.Invoice, VersionAdmin)
admin.site.register(models.EventCheckIn)
@transaction.atomic() # Copied from django-extensions. GenericForeignKey support removed as unnecessary.

View File

@@ -235,6 +235,6 @@ class EventCheckInForm(forms.ModelForm):
class EditCheckInForm(forms.ModelForm):
class Meta:
class Meta:
model = models.EventCheckIn
fields = '__all__'

View File

@@ -0,0 +1,41 @@
# Generated by Django 3.2.19 on 2023-05-17 08:44
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def migrate_old_data(apps, schema_editor):
EventChecklist = apps.get_model('RIGS', 'EventChecklist')
EventCheckIn = apps.get_model('RIGS', 'EventCheckIn')
for ec in EventChecklist.objects.all():
for crew in ec.crew.all():
vehicle = ec.vehicles.get(driver=crew.crewmember) or None
EventCheckIn.objects.create(event=ec.event, person=crew.crewmember, role=crew.role, time=crew.start, end_time=crew.end, vehicle=vehicle.vehicle)
def revert(apps, schema_editor):
apps.get_model('RIGS', 'EventCheckIn').objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0046_create_powertests'),
]
operations = [
migrations.CreateModel(
name='EventCheckIn',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.DateTimeField()),
('role', models.CharField(blank=True, max_length=50)),
('vehicle', models.CharField(blank=True, max_length=100)),
('end_time', models.DateTimeField(blank=True, null=True)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.event')),
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checkins', to=settings.AUTH_USER_MODEL)),
],
),
migrations.RunPython(migrate_old_data, reverse_code=revert),
]

View File

@@ -1,30 +1,17 @@
# Generated by Django 3.2.16 on 2023-05-10 17:23
# Generated by Django 3.2.19 on 2023-05-18 11:56
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0046_create_powertests'),
('RIGS', '0047_auto_20230517_0944'),
]
operations = [
migrations.CreateModel(
name='EventCheckIn',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.DateTimeField()),
('role', models.CharField(blank=True, max_length=50)),
('vehicle', models.CharField(blank=True, max_length=100)),
('end_time', models.DateTimeField(null=True)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.event')),
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checkins', to=settings.AUTH_USER_MODEL)),
],
),
migrations.RemoveField(
migrations.RemoveField(
model_name='eventchecklistvehicle',
name='checklist',
),
@@ -145,6 +132,21 @@ class Migration(migrations.Migration):
name='power_mic',
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC?', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
),
migrations.AlterField(
model_name='eventchecklist',
name='reviewed_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='powertestrecord',
name='reviewed_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='riskassessment',
name='reviewed_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.DeleteModel(
name='EventChecklistCrew',
),

View File

@@ -409,7 +409,15 @@ class Event(models.Model, RevisionMixin):
@property
def hs_done(self):
return self.riskassessment is not None and len(self.checklists.all()) > 0
return self.riskassessment is not None and self.has_checklist and self.has_power
@property
def has_checklist(self):
return self.checklists.exists()
@property
def has_power(self):
return self.power_tests.exists()
@property
def has_start_time(self):
@@ -703,17 +711,17 @@ def validate_url(value):
class ReviewableModel(models.Model):
reviewed_at = models.DateTimeField(null=True)
reviewed_at = models.DateTimeField(null=True, blank=True)
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
verbose_name="Reviewer", on_delete=models.CASCADE)
class Meta:
abstract = True
@cached_property
def fieldz(self):
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
class Meta:
abstract = True
@reversion.register
class RiskAssessment(ReviewableModel, RevisionMixin):
@@ -813,6 +821,12 @@ class RiskAssessment(ReviewableModel, RevisionMixin):
def get_event_size_display(self):
return self.SIZES[self.event_size][1] + " Event"
def __str__(self):
return f"{self.pk} | {self.event}"
def get_absolute_url(self):
return reverse('ra_detail', kwargs={'pk': self.pk})
@property
def activity_feed_string(self):
return str(self.event)
@@ -821,12 +835,6 @@ class RiskAssessment(ReviewableModel, RevisionMixin):
def name(self):
return str(self)
def get_absolute_url(self):
return reverse('ra_detail', kwargs={'pk': self.pk})
def __str__(self):
return f"{self.pk} | {self.event}"
@reversion.register
class EventChecklist(ReviewableModel, RevisionMixin):
@@ -854,6 +862,9 @@ class EventChecklist(ReviewableModel, RevisionMixin):
('review_eventchecklist', 'Can review Event Checklists')
]
def __str__(self):
return f"{self.pk} - {self.event}"
@property
def activity_feed_string(self):
return str(self.event)
@@ -861,15 +872,12 @@ class EventChecklist(ReviewableModel, RevisionMixin):
def get_absolute_url(self):
return reverse('ec_detail', kwargs={'pk': self.pk})
def __str__(self):
return f"{self.pk} - {self.event}"
@reversion.register
class PowerTestRecord(ReviewableModel, RevisionMixin):
event = models.ForeignKey('Event', related_name='power_tests', on_delete=models.CASCADE)
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='checklists',
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?")
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?")
venue = models.ForeignKey('Venue', on_delete=models.CASCADE)
notes = models.TextField(blank=True, default='')
# Small Electrical Checks
@@ -907,6 +915,12 @@ class PowerTestRecord(ReviewableModel, RevisionMixin):
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>")
class Meta:
ordering = ['event']
permissions = [
('review_power', 'Can review Power Test Records')
]
def __str__(self):
return f"{self.pk} - {self.event}"
@@ -914,12 +928,6 @@ class PowerTestRecord(ReviewableModel, RevisionMixin):
def activity_feed_string(self):
return str(self.event)
class Meta:
ordering = ['event']
permissions = [
('review_power', 'Can review Power Test Records')
]
class EventCheckIn(models.Model):
event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE)
@@ -927,7 +935,10 @@ class EventCheckIn(models.Model):
time = models.DateTimeField()
role = models.CharField(max_length=50, blank=True)
vehicle = models.CharField(max_length=100, blank=True)
end_time = models.DateTimeField(null=True)
end_time = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f"{self.person} on {self.event}"
def clean(self):
sass = " Please invent time travel and retry."
@@ -936,8 +947,8 @@ class EventCheckIn(models.Model):
if self.end_time and self.end_time < self.time:
raise ValidationError("May not check out before you've checked in." + sass)
def get_absolute_url(self):
return reverse('event_detail', kwargs={'pk': self.event_id})
def active(self):
return end_time is not None
def get_absolute_url(self):
return reverse('event_detail', kwargs={'pk': self.event.pk})

View File

@@ -13,22 +13,19 @@
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}"></script>
<script src="{% static 'js/interaction.js' %}"></script>
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
{% endblock %}
{% block content %}
<div class="col-12">
{% include 'form_errors.html' %}
{% if edit %}
<form role="form" method="POST" action="{% url 'ec_edit' pk=object.pk %}">
{% else %}
<form role="form" method="POST" action="{% url 'event_ec' pk=event.pk %}">
{% endif %}
<form role="form" method="POST" action="{% if edit %}{% url 'ec_edit' pk=object.pk %}{% else %}{% url 'event_ec' pk=event.pk %}{% endif %}">
<input type="hidden" name="{{ form.event.name }}" id="{{ form.event.id_for_label }}"
value="{{event.pk}}"/>
{% csrf_token %}
@@ -59,7 +56,7 @@
<div class="form-group form-row" id="{{ form.venue.id_for_label }}-group">
<label for="{{ form.venue.id_for_label }}"
class="col-4 col-form-label">{{ form.venue.label }}</label>
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker col-8" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
{% if venue %}
<option value="{{venue.pk}}" selected="selected">{{ venue.name }}</option>
{% elif event.venue %}

View File

@@ -15,19 +15,21 @@
{% endif %}
{% endif %}
{% if not event.dry_hire %}
{% if event.riskassessment %}
{% if event.has_checklist %}
<a href="{{ event.riskassessment.get_absolute_url }}"><span class="badge badge-success">RA: <span class="fas fa-check{% if event.riskassessment.reviewed_by %}-double{%endif%}"></span></a>
{% else %}
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
{% endif %}
{% endif %}
{% if not event.dry_hire %}
{% if event.hs_done %}
{# TODO Display status of all checklists #}
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span></span>
{% if event.has_checklist %}
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span> {% if event.checklists.count > 1 %}({{event.checklists.count}}){% endif %}</span>
{% else %}
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
{% endif %}
{% if event.has_power %}
<span class="badge badge-success">Power Record: <span class="fas fa-check"></span> {% if event.power_tests.count > 1 %}({{event.power_tests.count}}){% endif %}</span>
{% else %}
<span class="badge badge-danger">Power Record: <span class="fas fa-times"></span></span>
{% endif %}
{% endif %}
{% if perms.RIGS.view_invoice %}
{% if event.invoice %}

View File

@@ -101,13 +101,13 @@ urlpatterns = [
path('event/power/<int:pk>/review/', permission_required_with_403('RIGS.review_power')(views.MarkReviewed.as_view()),
name='pt_review', kwargs={'model': 'PowerTestRecord'}),
path('event/<int:pk>/checkin/', permission_required_with_403('RIGS.add_eventcheckin')(views.EventCheckIn.as_view()),
path('event/<int:pk>/checkin/', login_required(views.EventCheckIn.as_view()),
name='event_checkin'),
path('event/checkout/', permission_required_with_403('RIGS.change_eventcheckin')(views.EventCheckOut.as_view()),
path('event/checkout/', login_required(views.EventCheckOut.as_view()),
name='event_checkout'),
path('event/<int:pk>/checkin/edit/', permission_required_with_403('RIGS.change_eventcheckin')(views.EventCheckInEdit.as_view()),
path('event/<int:pk>/checkin/edit/', login_required(views.EventCheckInEdit.as_view()),
name='edit_checkin'),
path('event/<int:pk>/checkin/add/', permission_required_with_403('RIGS.add_eventcheckin')(views.EventCheckInOverride.as_view()),
path('event/<int:pk>/checkin/add/', login_required(views.EventCheckInOverride.as_view()),
name='event_checkin_override'),
# Finance

View File

@@ -1,7 +1,7 @@
from django.apps import apps
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.urls import reverse
from django.utils import timezone
from django.views import generic
from reversion import revisions as reversion
@@ -9,6 +9,7 @@ from reversion import revisions as reversion
from RIGS import models, forms
from RIGS.views.rigboard import get_related
from PyRIGS.views import PrintView, ModalURLMixin
from django.shortcuts import redirect
class HSCreateView(generic.CreateView):
@@ -24,7 +25,7 @@ class HSCreateView(generic.CreateView):
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
context['event'] = event
context['page_title'] = f'Create {self} for Event {event.display_id}'
context['page_title'] = f'Create {self.model.__name__} for Event {event.display_id}'
return context
@@ -36,7 +37,7 @@ class MarkReviewed(generic.View):
obj.reviewed_by = self.request.user
obj.reviewed_at = timezone.now()
obj.save()
return HttpResponseRedirect(reverse_lazy('hs_list'))
return HttpResponseRedirect(reverse('hs_list'))
class EventRiskAssessmentCreate(HSCreateView):
@@ -52,12 +53,12 @@ class EventRiskAssessmentCreate(HSCreateView):
ra = models.RiskAssessment.objects.filter(event=event).first()
if ra is not None:
return HttpResponseRedirect(reverse_lazy('ra_edit', kwargs={'pk': ra.pk}))
return HttpResponseRedirect(reverse('ra_edit', kwargs={'pk': ra.pk}))
return super(EventRiskAssessmentCreate, self).get(self)
def get_success_url(self):
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
return reverse('ra_detail', kwargs={'pk': self.object.pk})
class EventRiskAssessmentEdit(generic.UpdateView):
@@ -70,7 +71,7 @@ class EventRiskAssessmentEdit(generic.UpdateView):
ra.reviewed_by = None
ra.reviewed_at = None
ra.save()
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
return reverse('ra_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs)
@@ -113,7 +114,7 @@ class EventChecklistEdit(generic.UpdateView):
ec.reviewed_by = None
ec.reviewed_at = None
ec.save()
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
return reverse('ec_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(EventChecklistEdit, self).get_context_data(**kwargs)
@@ -141,12 +142,12 @@ class EventChecklistCreate(HSCreateView):
if ra is None:
messages.error(self.request, f'A Risk Assessment must exist prior to creating any Event Checklists for {event}! Please create one now.')
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
return HttpResponseRedirect(reverse('event_ra', kwargs={'pk': epk}))
return super(EventChecklistCreate, self).get(self)
def get_success_url(self):
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
return reverse('ec_detail', kwargs={'pk': self.object.pk})
class PowerTestDetail(generic.DetailView):
@@ -169,7 +170,7 @@ class PowerTestEdit(generic.UpdateView):
ec.reviewed_by = None
ec.reviewed_at = None
ec.save()
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
return reverse('ec_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -196,12 +197,12 @@ class PowerTestCreate(HSCreateView):
if ra is None:
messages.error(self.request, f'A Risk Assessment must exist prior to creating any Power Test Records for {event}! Please create one now.')
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
return HttpResponseRedirect(reverse('event_ra', kwargs={'pk': epk}))
return super().get(self)
def get_success_url(self):
return reverse_lazy('pt_detail', kwargs={'pk': self.object.pk})
return reverse('pt_detail', kwargs={'pk': self.object.pk})
class HSList(generic.ListView):
@@ -234,7 +235,7 @@ class EventCheckIn(generic.CreateView, ModalURLMixin):
form_class = forms.EventCheckInForm
def get_success_url(self):
return self.get_close_url('event_detail', 'event_detail') # Well, that's one way of doing that...!
return self.get_close_url('event_detail', 'event_detail') # Well, that's one way of doing that...!
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -262,8 +263,14 @@ class EventCheckInEdit(generic.UpdateView, ModalURLMixin):
template_name = 'hs/eventcheckin_form.html'
form_class = forms.EditCheckInForm
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if not obj.person == self.request.user and not obj.event.mic == self.request.user:
return redirect(self.request.META.get('HTTP_REFERER', '/'))
return super().dispatch(request)
def get_success_url(self):
return self.get_close_url('event_detail', 'event_detail') # Well, that's one way of doing that...!
return self.get_close_url('event_detail', 'event_detail') # Well, that's one way of doing that...!
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -280,4 +287,4 @@ class EventCheckOut(generic.RedirectView):
if checkin:
checkin.end_time = timezone.now()
checkin.save()
return reverse_lazy('event_detail', kwargs={'pk': checkin.event.pk})
return self.request.META.get('HTTP_REFERER', '/')

View File

@@ -73,7 +73,7 @@ function initPicker(obj) {
return array;
}
};
console.log(obj.data);
//console.log(obj.data);
if (!obj.data('noclear')) {
obj.prepend($("<option></option>")
.attr("value",'')