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 captcha.fields import ReCaptchaField
from reversion import revisions as reversion from reversion import revisions as reversion
import simplejson import simplejson
from datetime import datetime
from RIGS import models from RIGS import models
@@ -172,17 +173,17 @@ class EventRiskAssessmentForm(forms.ModelForm):
class EventChecklistForm(forms.ModelForm): class EventChecklistForm(forms.ModelForm):
# Parsed from incoming form data by clean, then saved into models when the form is saved
items = {} items = {}
# There's probably a thousand better ways to do this, but this one is mine
def clean(self): def clean(self):
vehicles = {key: val for key, val in self.data.items() vehicles = {key: val for key, val in self.data.items()
if key.startswith('vehicle')} if key.startswith('vehicle')}
drivers = {key: val for key, val in self.data.items()
if key.startswith('driver')}
for key in vehicles: for key in vehicles:
pk = int(key.split('_')[1]) pk = int(key.split('_')[1])
driver_key = 'driver_' + str(pk) 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') raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
else: else:
try: try:
@@ -191,10 +192,36 @@ class EventChecklistForm(forms.ModelForm):
item = models.EventChecklistVehicle() item = models.EventChecklistVehicle()
item.vehicle = vehicles['vehicle_' + str(pk)] 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 # 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() return super(EventChecklistForm, self).clean()
@@ -203,15 +230,18 @@ class EventChecklistForm(forms.ModelForm):
if (commit): if (commit):
# Remove all existing, to be recreated from the form # Remove all existing, to be recreated from the form
checklist.vehicles.all().delete() checklist.vehicles.all().delete()
checklist.save() checklist.crew.all().delete()
for key in self.items: for key in self.items:
item = self.items[key] item = self.items[key]
reversion.add_to_revision(item) reversion.add_to_revision(item)
# finish and save new database items # finish and save new database items
item.checklist = checklist item.checklist = checklist
item.full_clean()
item.save() item.save()
checklist.save()
self.items.clear() self.items.clear()
return checklist 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) return "%i - %s" % (self.pk, self.event)
@reversion.register(follow=['vehicles', ]) @reversion.register(follow=['vehicles', 'crew'])
class EventChecklist(models.Model, RevisionMixin): class EventChecklist(models.Model, RevisionMixin):
event = models.OneToOneField('Event', on_delete=models.CASCADE) event = models.OneToOneField('Event', on_delete=models.CASCADE)
@@ -680,9 +680,23 @@ class EventChecklist(models.Model, RevisionMixin):
class EventChecklistVehicle(models.Model): class EventChecklistVehicle(models.Model):
checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE) checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE)
vehicle = models.CharField(max_length=255) 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 reversion_hide = True
def __str__(self): def __str__(self):
return "{} driven by {}".format(self.vehicle, str(self.driver)) 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> </dl>
</div> </div>
</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 card-default mb-3">
<div class="card-header">Power</div> <div class="card-header">Power</div>
<div class="card-body"> <div class="card-body">

View File

@@ -35,9 +35,11 @@
{% if object.medium_event %} {% if object.medium_event %}
$('#small-event').slideUp(); $('#small-event').slideUp();
$('#medium-event').slideDown(); $('#medium-event').slideDown();
$('#size-selector button[data-event-size=1]').addClass('active');
{% else %} {% else %}
$('#small-event').slideDown(); $('#small-event').slideDown();
$('#medium-event').slideUp(); $('#medium-event').slideUp();
$('#size-selector button[data-event-size=0]').addClass('active');
{% endif%} {% endif%}
{% endif %} {% endif %}
$('#size-selector button').on('click', function () { $('#size-selector button').on('click', function () {
@@ -53,24 +55,29 @@
$('#medium-event').slideUp(); $('#medium-event').slideUp();
} }
}); });
$('#vehicle-add').on('click', function (event) { $('button[data-action=add]').on('click', function (event) {
event.preventDefault(); event.preventDefault();
var newID = Number($('#vehiclest').attr('data-pk')); var target = $($(this).attr('data-target'));
$('#vehicles_new').clone().attr('style', "").attr('id', 'vehicles_' + newID).appendTo('#vehiclest'); var newID = Number(target.attr('data-pk'));
$('#vehicles_' + newID).find('select,input').attr('name', function(){ var newRow = $($(this).attr('data-clone'))
return this.name.split('_')[0] + '_' + newID; .clone().attr('style', "")
//Disabled prevents the hidden row being sent to the form .attr('id', function(i, val){
}).removeAttr('disabled'); return val.split("_")[0] + '_' + newID;
$('#vehicles_' + newID).find('button[data-action=delete]').attr('data-id', newID); })
$('#vehicles_' + newID).find('select').addClass('selectpicker'); .appendTo(target);
$('#vehicles_' + newID).find('.selectpicker').selectpicker('refresh'); 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))}); $(".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) { $('button[data-action=delete]').on('click', function(event) {
event.preventDefault(); event.preventDefault();
console.log($(this).attr('data-id')); $($(this).attr('data-target') + '_' + $(this).attr('data-id')).remove();
$('#vehicles_' + $(this).attr('data-id')).remove();
}); });
}); });
</script> </script>
@@ -124,11 +131,8 @@
<tbody id="vehiclest" data-pk="-1"> <tbody id="vehiclest" data-pk="-1">
<tr id="vehicles_new" style="display: none;"> <tr id="vehicles_new" style="display: none;">
<td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td> <td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td>
<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>
<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"> <td><button class="btn btn-danger" data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button</td>
</select>
</td>
<td><button class="btn btn-danger" data-action='delete'><span class="fas fa-times"></span></button</td>
</tr> </tr>
{# TODO Add required to all fields on row when one is edited #} {# TODO Add required to all fields on row when one is edited #}
{% for i in object.vehicles.all %} {% for i in object.vehicles.all %}
@@ -141,13 +145,13 @@
{% endif %} {% endif %}
</select> </select>
</td> </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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="text-right"> <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> </div>
</div> </div>
@@ -184,23 +188,38 @@
<th scope="col">Start Time</th> <th scope="col">Start Time</th>
<th scope="col">Role</th> <th scope="col">Role</th>
<th scope="col">End Time</th> <th scope="col">End Time</th>
<th scope="col"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="crewmemberst" data-pk="-1">
{% for i in '012'|make_list %} <tr id="crew_new" style="display: none;">
<tr> <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>
<th scope="row"> </td>
</th> <td><input name="start_new" type="datetime-local" class="form-control" value="{{ i.start }}" disabled="true"/></td>
<td><input type="time" class="form-control"/></td> <td><input name="role_new" type="text" class="form-control" value="{{ i.role }}" disabled="true"/></td>
<td><input type="text" class="form-control"/></td> <td><input name="end_new" type="datetime-local" class="form-control" value="{{ i.end }}" disabled="true" /></td>
<td><input type="time" class="form-control"/></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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="text-right"> <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> </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> <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="col-sm">
<div class="card"> <div class="card">
<div class="card-header"> <h4 class="card-header">Rigboard</h4>
<h4 class="list-group-item-heading">Rigboard</h4>
</div>
<div class="list-group list-group-flush"> <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 '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> <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> <a class="list-group-item list-group-item-action" href="{% url 'event_create' %}"><i class="fas fa-plus"></i> New Event</a>
{% endif %} {% endif %}
</div> </div>
<div class="card-header"> <h4 class="card-header">Asset Database</h4>
<h4 class="list-group-item-heading">Asset Database</h4>
</div>
<div class="list-group list-group-flush"> <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> <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 %} {% 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> <a class="list-group-item list-group-item-action" href="{% url 'supplier_create' %}"><i class="fas fa-plus"></i> New Supplier</a>
{% endif %} {% endif %}
</div> </div>
<div class="card-header"> <h4 class="card-header">Quick Links</h4>
<h4 class="list-group-item-heading">Quick Links</h4>
</div>
<div class="list-group list-group-flush"> <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="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> <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 %} {% if version.changes.anything_changed or version.changes.old == None %}
{% for change in version.changes.field_changes %} {% 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 %} {% endfor %}
{% for itemChange in version.changes.item_changes %} {% 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 %} <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"> <ul class="list-group list-group-flush">
{% for change in itemChange.field_changes %} {% for change in itemchange.field_changes %}
<li class="list-group-item"> <li class="list-group-item">
<h4 class="list-group-item-heading">{{ change.field.verbose_name }}</h4> <p>{{ change.field.verbose_name|title }}:</p>
<div class="dont-break-out">{% include 'version_changes_change.html' with change=itemChange %}</div> <div class="dont-break-out">{% include 'partials/version_changes_change.html' with change=itemchange %}</div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endspaceless %}'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</span> {% endspaceless %}'>{{ itemchange.name }}</span>
{% endfor %} {% endfor %}
{% else %} {% else %}
nothing useful nothing useful

View File

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