Event checklist crew works

Mostly - its not happy with timezones
This commit is contained in:
2020-08-29 21:52:09 +01:00
parent 1feb9449ed
commit dbe0d35400
9 changed files with 177 additions and 76 deletions

View File

@@ -10,6 +10,7 @@ from django.contrib.auth.forms import AuthenticationForm
from captcha.fields import ReCaptchaField
from reversion import revisions as reversion
import simplejson
from datetime import datetime
from RIGS import models
@@ -172,17 +173,17 @@ class EventRiskAssessmentForm(forms.ModelForm):
class EventChecklistForm(forms.ModelForm):
# Parsed from incoming form data by clean, then saved into models when the form is saved
items = {}
# There's probably a thousand better ways to do this, but this one is mine
def clean(self):
vehicles = {key: val for key, val in self.data.items()
if key.startswith('vehicle')}
drivers = {key: val for key, val in self.data.items()
if key.startswith('driver')}
for key in vehicles:
pk = int(key.split('_')[1])
driver_key = 'driver_' + str(pk)
if(drivers[driver_key] == ''):
if(self.data[driver_key] == ''):
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
else:
try:
@@ -191,10 +192,36 @@ class EventChecklistForm(forms.ModelForm):
item = models.EventChecklistVehicle()
item.vehicle = vehicles['vehicle_' + str(pk)]
item.driver = models.Profile.objects.get(pk=drivers['driver_' + str(pk)])
item.driver = models.Profile.objects.get(pk=self.data[driver_key])
item.full_clean('checklist')
# item does not have a database pk yet as it isn't saved
self.items[pk] = item
self.items['v' + str(pk)] = item
crewmembers = {key: val for key, val in self.data.items()
if key.startswith('crewmember')}
other_fields = ['start','role','end']
for key in crewmembers:
pk = int(key.split('_')[1])
for field in other_fields:
value = self.data['{}_{}'.format(field,pk)]
if value == '':
raise forms.ValidationError('Add a {} to crewmember {}'.format(field, pk), code='{}_mismatch'.format(field))
try:
item = models.EventChecklistCrew.objects.get(pk=pk)
except models.EventChecklistCrew.DoesNotExist:
item = models.EventChecklistCrew()
item.crewmember = models.Profile.objects.get(pk=self.data['crewmember_' + str(pk)])
item.start = self.data['start_' + str(pk)]
item.role = self.data['role_' + str(pk)]
item.end = self.data['end_' + str(pk)]
item.full_clean('checklist')
# item does not have a database pk yet as it isn't saved
self.items['c' + str(pk)] = item
return super(EventChecklistForm, self).clean()
@@ -203,15 +230,18 @@ class EventChecklistForm(forms.ModelForm):
if (commit):
# Remove all existing, to be recreated from the form
checklist.vehicles.all().delete()
checklist.save()
checklist.crew.all().delete()
for key in self.items:
item = self.items[key]
reversion.add_to_revision(item)
# finish and save new database items
item.checklist = checklist
item.full_clean()
item.save()
checklist.save()
self.items.clear()
return checklist

View File

@@ -0,0 +1,31 @@
# Generated by Django 3.1 on 2020-08-29 20:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0046_auto_20200828_1246'),
]
operations = [
migrations.AlterField(
model_name='eventchecklistvehicle',
name='driver',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to=settings.AUTH_USER_MODEL),
),
migrations.CreateModel(
name='EventChecklistCrew',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(max_length=255, null=True)),
('start', models.DateTimeField(blank=True, null=True)),
('end', models.DateTimeField(blank=True, null=True)),
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
('crewmember', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crewed', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -635,7 +635,7 @@ class RiskAssessment(models.Model, RevisionMixin):
return "%i - %s" % (self.pk, self.event)
@reversion.register(follow=['vehicles', ])
@reversion.register(follow=['vehicles', 'crew'])
class EventChecklist(models.Model, RevisionMixin):
event = models.OneToOneField('Event', on_delete=models.CASCADE)
@@ -680,9 +680,23 @@ class EventChecklist(models.Model, RevisionMixin):
class EventChecklistVehicle(models.Model):
checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE)
vehicle = models.CharField(max_length=255)
driver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='drivers', on_delete=models.CASCADE)
driver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='vehicles', on_delete=models.CASCADE)
reversion_hide = True
def __str__(self):
return "{} driven by {}".format(self.vehicle, str(self.driver))
@reversion.register
class EventChecklistCrew(models.Model):
checklist = models.ForeignKey('EventChecklist', related_name='crew', blank=True, on_delete=models.CASCADE)
crewmember = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='crewed', on_delete=models.CASCADE)
role = models.CharField(max_length=255)
start = models.DateTimeField()
end = models.DateTimeField()
reversion_hide = True
def __str__(self):
return "{} ({})".format(str(self.crewmember), self.role)

View File

@@ -61,6 +61,16 @@
</dl>
</div>
</div>
<div class="card card-default mb-3">
<div class="card-header">Crew Record</div>
<div class="card-body">
<ul>
{% for i in object.crew.all %}
<li>{{i}}</li>
{% endfor %}
</ul>
</div>
</div>
<div class="card card-default mb-3">
<div class="card-header">Power</div>
<div class="card-body">

View File

@@ -35,9 +35,11 @@
{% if object.medium_event %}
$('#small-event').slideUp();
$('#medium-event').slideDown();
$('#size-selector button[data-event-size=1]').addClass('active');
{% else %}
$('#small-event').slideDown();
$('#medium-event').slideUp();
$('#size-selector button[data-event-size=0]').addClass('active');
{% endif%}
{% endif %}
$('#size-selector button').on('click', function () {
@@ -53,24 +55,29 @@
$('#medium-event').slideUp();
}
});
$('#vehicle-add').on('click', function (event) {
$('button[data-action=add]').on('click', function (event) {
event.preventDefault();
var newID = Number($('#vehiclest').attr('data-pk'));
$('#vehicles_new').clone().attr('style', "").attr('id', 'vehicles_' + newID).appendTo('#vehiclest');
$('#vehicles_' + newID).find('select,input').attr('name', function(){
return this.name.split('_')[0] + '_' + newID;
//Disabled prevents the hidden row being sent to the form
}).removeAttr('disabled');
$('#vehicles_' + newID).find('button[data-action=delete]').attr('data-id', newID);
$('#vehicles_' + newID).find('select').addClass('selectpicker');
$('#vehicles_' + newID).find('.selectpicker').selectpicker('refresh');
var target = $($(this).attr('data-target'));
var newID = Number(target.attr('data-pk'));
var newRow = $($(this).attr('data-clone'))
.clone().attr('style', "")
.attr('id', function(i, val){
return val.split("_")[0] + '_' + newID;
})
.appendTo(target);
newRow.find('select,input').attr('name', function(i, val){
return val.split("_")[0] + '_' + newID;
})//Disabled is to prevent the hidden row being sent to the form
.removeAttr('disabled');
newRow.find('button[data-action=delete]').attr('data-id', newID);
newRow.find('select').addClass('selectpicker');
newRow.find('.selectpicker').selectpicker('refresh');
$(".selectpicker").each(function(){initPicker($(this))});
$('#vehiclest').attr('data-pk', newID - 1);
$(target).attr('data-pk', newID - 1);
});
$('button[data-action=delete]').on('click', function(event) {
event.preventDefault();
console.log($(this).attr('data-id'));
$('#vehicles_' + $(this).attr('data-id')).remove();
$($(this).attr('data-target') + '_' + $(this).attr('data-id')).remove();
});
});
</script>
@@ -124,11 +131,8 @@
<tbody id="vehiclest" data-pk="-1">
<tr id="vehicles_new" style="display: none;">
<td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td>
<td>
<select class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true">
</select>
</td>
<td><button class="btn btn-danger" data-action='delete'><span class="fas fa-times"></span></button</td>
<td><select class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true"></select></td>
<td><button class="btn btn-danger" data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button</td>
</tr>
{# TODO Add required to all fields on row when one is edited #}
{% for i in object.vehicles.all %}
@@ -141,13 +145,13 @@
{% endif %}
</select>
</td>
<td><button class="btn btn-danger" data-id='{{i.pk}}' data-action='delete'><span class="fas fa-times"></span></button</td>
<td><button class="btn btn-danger" data-id='{{i.pk}}' data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="text-right">
<button class="btn btn-secondary" id="vehicle-add"><span class="fas fa-plus"></span> Add Vehicle</button>
<button class="btn btn-secondary" id="vehicle-add" data-action='add' data-target='#vehiclest' data-clone='#vehicles_new'><span class="fas fa-plus"></span> Add Vehicle</button>
</div>
</div>
</div>
@@ -184,23 +188,38 @@
<th scope="col">Start Time</th>
<th scope="col">Role</th>
<th scope="col">End Time</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for i in '012'|make_list %}
<tr>
<th scope="row">
</th>
<td><input type="time" class="form-control"/></td>
<td><input type="text" class="form-control"/></td>
<td><input type="time" class="form-control"/></td>
<tbody id="crewmemberst" data-pk="-1">
<tr id="crew_new" style="display: none;">
<td>
<select name="crewmember_new" class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" disabled="true"></select>
</td>
<td><input name="start_new" type="datetime-local" class="form-control" value="{{ i.start }}" disabled="true"/></td>
<td><input name="role_new" type="text" class="form-control" value="{{ i.role }}" disabled="true"/></td>
<td><input name="end_new" type="datetime-local" class="form-control" value="{{ i.end }}" disabled="true" /></td>
<td><button class="btn btn-danger" data-id='{{crew.pk}}' data-action='delete' data-target='#crewmember'><span class="fas fa-times"></span></button</td>
</tr>
{% for crew in object.crew.all %}
<tr id="crew_{{crew.pk}}">
<td>
<select name="crewmember_{{crew.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if crew.crewmember != '' %}
<option value="{{crew.crewmember.pk}}" selected="selected">{{ crew.crewmember.name }}</option>
{% endif %}
</select>
</td>
<td><input name="start_{{crew.pk}}" type="datetime-local" class="form-control" value="{{ crew.start|date:'Y-m-d' }}T{{ crew.start|date:'H:i:s' }}"/></td>
<td><input name="role_{{crew.pk}}" type="text" class="form-control" value="{{ crew.role }}"/></td>
<td><input name="end_{{crew.pk}}" type="datetime-local" class="form-control" value="{{ crew.end|date:'Y-m-d' }}T{{ crew.end|date:'H:i:s' }}"/></td>
<td><button class="btn btn-danger" data-id='{{crew.pk}}' data-action='delete' data-target='#crewmember'><span class="fas fa-times"></span></button</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="text-right">
<button class="btn btn-secondary"><span class="fas fa-plus"></span> Add Crewmember</button>
<button class="btn btn-secondary" data-action='add' data-target='#crewmemberst' data-clone='#crew_new'><span class="fas fa-plus"></span> Add Crewmember</button>
</div>
</div>
</div>

View File

@@ -9,9 +9,7 @@
<h4 class="col-sm-12 pb-3">Welcome back {{ user.get_full_name }}, there {%if rig_count == 1 %}is one rig coming up{%else%}are {{ rig_count|apnumber }} rigs coming up.{%endif%}</h4>
<div class="col-sm">
<div class="card">
<div class="card-header">
<h4 class="list-group-item-heading">Rigboard</h4>
</div>
<h4 class="card-header">Rigboard</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'rigboard' %}"><i class="fas fa-list"></i> Rigboard</a>
<a class="list-group-item list-group-item-action" href="{% url 'web_calendar' %}"><i class="fas fa-calendar"></i> Calendar</a>
@@ -19,9 +17,7 @@
<a class="list-group-item list-group-item-action" href="{% url 'event_create' %}"><i class="fas fa-plus"></i> New Event</a>
{% endif %}
</div>
<div class="card-header">
<h4 class="list-group-item-heading">Asset Database</h4>
</div>
<h4 class="card-header">Asset Database</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'asset_index' %}"><i class="fas fa-tag"></i> Asset List </a>
{% if perms.assets.add_asset %}
@@ -32,9 +28,7 @@
<a class="list-group-item list-group-item-action" href="{% url 'supplier_create' %}"><i class="fas fa-plus"></i> New Supplier</a>
{% endif %}
</div>
<div class="card-header">
<h4 class="list-group-item-heading">Quick Links</h4>
</div>
<h4 class="card-header">Quick Links</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="https://forum.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><i class="fas fa-comment-alt"></i> TEC Forum</a>
<a class="list-group-item list-group-item-action" href="//members.nottinghamtec.co.uk/wiki" target="_blank" rel="noopener noreferrer"><i class="fas fa-pen-square"></i> TEC Wiki</a>

View File

@@ -1,18 +1,18 @@
{% if version.changes.anything_changed or version.changes.old == None %}
{% for change in version.changes.field_changes %}
<span title="Changes to {{ change.field.verbose_name }}" class="badge badge-info p-2" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='{% spaceless %}{% include "version_changes_change.html" %}{% endspaceless %}'>{{ change.field.verbose_name }}</span>
<span title="Changes to {{ change.field.verbose_name }}" class="badge badge-info p-2" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='{% spaceless %}{% include "partials/version_changes_change.html" %}{% endspaceless %}'>{{ change.field.verbose_name }}</span>
{% endfor %}
{% for itemChange in version.changes.item_changes %}
<span title="Changes to item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'" class="badge badge-info p-1" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='{% spaceless %}
<ul class="list-group">
{% for change in itemChange.field_changes %}
{% for itemchange in version.changes.item_changes %}
<span title="Changes to {{ itemchange.name }}" class="badge badge-info p-2" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='{% spaceless %}
<ul class="list-group list-group-flush">
{% for change in itemchange.field_changes %}
<li class="list-group-item">
<h4 class="list-group-item-heading">{{ change.field.verbose_name }}</h4>
<div class="dont-break-out">{% include 'version_changes_change.html' with change=itemChange %}</div>
<p>{{ change.field.verbose_name|title }}:</p>
<div class="dont-break-out">{% include 'partials/version_changes_change.html' with change=itemchange %}</div>
</li>
{% endfor %}
</ul>
{% endspaceless %}'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</span>
{% endspaceless %}'>{{ itemchange.name }}</span>
{% endfor %}
{% else %}
nothing useful

View File

@@ -118,30 +118,34 @@ class ModelComparison(object):
@cached_property
def item_changes(self):
# Recieves two event version objects and compares their items, returns an array of ItemCompare objects
item_type = ContentType.objects.get_for_model(models.EventItem)
item_dict = {}
if hasattr(self.version, 'parent'):
old_item_versions = self.version.parent.revision.version_set.filter(content_type=item_type)
new_item_versions = self.version.revision.version_set.filter(content_type=item_type)
item_type = ContentType.objects.get_for_model(self.version.object)
old_item_versions = self.version.parent.revision.version_set.exclude(content_type=item_type)
new_item_versions = self.version.revision.version_set.exclude(content_type=item_type)
comparisonParams = {'excluded_keys': ['id', 'event', 'order']}
comparisonParams = {'excluded_keys': ['id', 'event', 'order']}
# Build some dicts of what we have
# build a list of items, key is the item_pk
for version in old_item_versions: # put all the old versions in a list
if version.field_dict["event_id"] == int(self.new.pk):
compare = ModelComparison(old=version._object_version.object, **comparisonParams)
item_dict[version.object_id] = compare
# Build some dicts of what we have
item_dict = {} # build a list of items, key is the item_pk
# FIXME Removing the if checks makes things REALLY slow...
for version in old_item_versions: # put all the old versions in a list
#if version.field_dict["event_id"] == int(self.new.pk):
compare = ModelComparison(old=version._object_version.object, **comparisonParams)
item_dict[version.object_id] = compare
for version in new_item_versions: # go through the new versions
if version.field_dict["event_id"] == int(self.new.pk):
try:
compare = item_dict[version.object_id] # see if there's a matching old version
compare.new = version._object_version.object # then add the new version to the dictionary
except KeyError: # there's no matching old version, so add this item to the dictionary by itself
compare = ModelComparison(new=version._object_version.object, **comparisonParams)
for version in new_item_versions: # go through the new versions
#if version.field_dict["event_id"] == int(self.new.pk):
try:
compare = item_dict[version.object_id] # see if there's a matching old version
compare.new = version._object_version.object # then add the new version to the dictionary
except KeyError: # there's no matching old version, so add this item to the dictionary by itself
compare = ModelComparison(new=version._object_version.object, **comparisonParams)
item_dict[version.object_id] = compare # update the dictionary with the changes
if compare.new:
compare.name = str(compare.new)
else:
compare.name = str(compare.old)
item_dict[version.object_id] = compare # update the dictionary with the changes
changes = []
for (_, compare) in list(item_dict.items()):
@@ -160,7 +164,6 @@ class ModelComparison(object):
class RIGSVersionManager(VersionQuerySet):
# TODO Rework this so its possible to define from the other apps
def get_for_multiple_models(self, model_array):
content_types = []
for model in model_array: