From 8ea96674db7a7a67d15786f224b0eee9932acefb Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Sat, 29 Aug 2020 13:56:57 +0100 Subject: [PATCH] Initial work on new checklist handling. No more JSON! --- RIGS/forms.py | 48 ++++++++++++++- RIGS/hs.py | 2 - RIGS/migrations/0046_auto_20200828_1246.py | 38 ++++++++++++ RIGS/models.py | 18 ++++-- RIGS/static/js/autocompleter.js | 2 +- RIGS/static/js/src/autocompleter.js | 1 - RIGS/templates/event_checklist_detail.html | 11 ++-- RIGS/templates/event_checklist_form.html | 58 ++++++++----------- RIGS/templates/hs_list.html | 8 ++- RIGS/templates/invoice_detail.html | 10 ++-- RIGS/templatetags/filters.py | 13 ----- RIGS/urls.py | 3 + versioning/templates/activity_feed_data.html | 2 +- .../{ => partials}/version_changes.html | 2 +- .../partials/version_history_table.html | 2 +- versioning/versioning.py | 38 ++++++------ 16 files changed, 163 insertions(+), 93 deletions(-) create mode 100644 RIGS/migrations/0046_auto_20200828_1246.py rename versioning/templates/{ => partials}/version_changes.html (92%) diff --git a/RIGS/forms.py b/RIGS/forms.py index c97e937a..33a26f15 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -4,9 +4,11 @@ from django.conf import settings from django.core import serializers from django.core.mail import EmailMessage, EmailMultiAlternatives from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm +from django.db import transaction from registration.forms import RegistrationFormUniqueEmail from django.contrib.auth.forms import AuthenticationForm from captcha.fields import ReCaptchaField +from reversion import revisions as reversion import simplejson from RIGS import models @@ -18,8 +20,6 @@ forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local' # Events Shit - - class EventForm(forms.ModelForm): datetime_input_formats = list(settings.DATETIME_INPUT_FORMATS) meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False) @@ -172,6 +172,50 @@ class EventRiskAssessmentForm(forms.ModelForm): class EventChecklistForm(forms.ModelForm): + items = {} + + 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] == ''): + raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch') + else: + try: + item = models.EventChecklistVehicle.objects.get(pk=pk) + except models.EventChecklistVehicle.DoesNotExist: + item = models.EventChecklistVehicle() + + item.vehicle = vehicles['vehicle_' + str(pk)] + item.driver = models.Profile.objects.get(pk=drivers['driver_' + str(pk)]) + + # item does not have a database pk yet as it isn't saved + self.items[pk] = item + + return super(EventChecklistForm, self).clean() + + def save(self, commit=True): + checklist = super(EventChecklistForm, self).save(commit=False) + if (commit): + # Remove all existing, to be recreated from the form + checklist.vehicles.all().delete() + checklist.save() + + for key in self.items: + item = self.items[key] + reversion.add_to_revision(item) + # finish and save new database items + item.checklist = checklist + item.save() + + self.items.clear() + + return checklist + class Meta: model = models.EventChecklist fields = '__all__' diff --git a/RIGS/hs.py b/RIGS/hs.py index b7da7638..db6c0177 100644 --- a/RIGS/hs.py +++ b/RIGS/hs.py @@ -100,7 +100,6 @@ class EventChecklistEdit(generic.UpdateView): ec = models.EventChecklist.objects.get(pk=pk) context['event'] = ec.event context['edit'] = True - context['vehicles_length'] = range(len(self.object.vehicles)) return context class EventChecklistCreate(generic.CreateView): @@ -132,7 +131,6 @@ class EventChecklistCreate(generic.CreateView): epk = self.kwargs.get('pk') event = models.Event.objects.get(pk=epk) context['event'] = event - context['vehicles_length'] = range(2) return context def get_success_url(self): diff --git a/RIGS/migrations/0046_auto_20200828_1246.py b/RIGS/migrations/0046_auto_20200828_1246.py new file mode 100644 index 00000000..18b7dfa7 --- /dev/null +++ b/RIGS/migrations/0046_auto_20200828_1246.py @@ -0,0 +1,38 @@ +# Generated by Django 3.1 on 2020-08-28 11:46 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0045_auto_20200824_1431'), + ] + + operations = [ + migrations.RemoveField( + model_name='eventchecklist', + name='vehicles', + ), + migrations.AlterField( + model_name='eventchecklist', + name='power_mic', + field=models.ForeignKey(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='profile', + name='first_name', + field=models.CharField(blank=True, max_length=150, verbose_name='first name'), + ), + migrations.CreateModel( + name='EventChecklistVehicle', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vehicle', models.CharField(max_length=255)), + ('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')), + ('driver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='drivers', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 2337932c..a3310bac 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -505,7 +505,6 @@ class EventAuthorisation(models.Model, RevisionMixin): return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')') -@reversion.register(follow=['payment_set']) class Invoice(models.Model): event = models.OneToOneField('Event', on_delete=models.CASCADE) invoice_date = models.DateField(auto_now_add=True) @@ -627,15 +626,14 @@ class RiskAssessment(models.Model, RevisionMixin): def __str__(self): return "%i - %s" % (self.pk, self.event) -@reversion.register +@reversion.register(follow=['vehicles',]) class EventChecklist(models.Model, RevisionMixin): - event = models.OneToOneField('Event', on_delete=models.CASCADE) # General - power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='checklist', + power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='checklists', null=True, verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?") - vehicles = models.JSONField(help_text="List vehicles and their drivers", default=dict, null=False) + #vehicles = models.JSONField(help_text="List vehicles and their drivers", default=dict, null=False) # Safety Checks safe_parking = models.BooleanField(help_text="Vehicles parked safely?
(does not obstruct venue access)") @@ -667,3 +665,13 @@ class EventChecklist(models.Model, RevisionMixin): def __str__(self): return "%i - %s" % (self.pk, self.event) + + +@reversion.register +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) + + def __str__(self): + return "{} driven by {}".format(self.vehicle, str(self.driver)) diff --git a/RIGS/static/js/autocompleter.js b/RIGS/static/js/autocompleter.js index eaa34554..8a9ad4ba 100644 --- a/RIGS/static/js/autocompleter.js +++ b/RIGS/static/js/autocompleter.js @@ -1 +1 @@ -function changeSelectedValue(e,t,a,l){e.find("option").remove(),e.append($("").attr("value",t).text(a).data("update_url",l)),e.selectpicker("render"),e.selectpicker("refresh"),e.selectpicker("val",t),e.change()}function refreshUpdateHref(e){targetObject=$("#"+e.attr("id")+"-update"),update_url=$("option:selected",e).data("update_url"),""==update_url?targetObject.attr("disabled",!0):(targetObject.attr("href",update_url),targetObject.attr("disabled",!1))}function initPicker(e){console.log("called!");var t={ajax:{url:e.data("sourceurl"),type:"GET",dataType:"json",data:{term:"{{{q}}}"}},locale:{emptyTitle:""},clearOnEmpty:!1,preprocessData:function(e){var t,a=e.length,l=[];if(l.push({text:clearSelectionLabel,value:"",data:{update_url:"",subtext:""}}),a)for(t=0;t").attr("value","").text(clearSelectionLabel).data("update_url","")),e.selectpicker().ajaxSelectPicker(t),e.change((function(){refreshUpdateHref(e)})),refreshUpdateHref(e)}$(document).ready((function(){clearSelectionLabel="(no selection)",$(".selectpicker").each((function(){initPicker($(this))})),$("#modal").on("hide.bs.modal",(function(e){null!=modaltarget&&""!=modalobject&&changeSelectedValue($(modaltarget),modalobject[0].pk,modalobject[0].fields.name,modalobject[0].update_url)}))})); \ No newline at end of file +function changeSelectedValue(e,t,a,r){e.find("option").remove(),e.append($("").attr("value",t).text(a).data("update_url",r)),e.selectpicker("render"),e.selectpicker("refresh"),e.selectpicker("val",t),e.change()}function refreshUpdateHref(e){targetObject=$("#"+e.attr("id")+"-update"),update_url=$("option:selected",e).data("update_url"),""==update_url?targetObject.attr("disabled",!0):(targetObject.attr("href",update_url),targetObject.attr("disabled",!1))}function initPicker(e){var t={ajax:{url:e.data("sourceurl"),type:"GET",dataType:"json",data:{term:"{{{q}}}"}},locale:{emptyTitle:""},clearOnEmpty:!1,preprocessData:function(e){var t,a=e.length,r=[];if(r.push({text:clearSelectionLabel,value:"",data:{update_url:"",subtext:""}}),a)for(t=0;t").attr("value","").text(clearSelectionLabel).data("update_url","")),e.selectpicker().ajaxSelectPicker(t),e.change((function(){refreshUpdateHref(e)})),refreshUpdateHref(e)}$(document).ready((function(){clearSelectionLabel="(no selection)",$(".selectpicker").each((function(){initPicker($(this))})),$("#modal").on("hide.bs.modal",(function(e){null!=modaltarget&&""!=modalobject&&changeSelectedValue($(modaltarget),modalobject[0].pk,modalobject[0].fields.name,modalobject[0].update_url)}))})); \ No newline at end of file diff --git a/RIGS/static/js/src/autocompleter.js b/RIGS/static/js/src/autocompleter.js index cc1c9938..d198f2e0 100644 --- a/RIGS/static/js/src/autocompleter.js +++ b/RIGS/static/js/src/autocompleter.js @@ -28,7 +28,6 @@ function refreshUpdateHref(obj) { } function initPicker(obj) { - console.log('called!'); var options = { ajax: { url: obj.data('sourceurl'), diff --git a/RIGS/templates/event_checklist_detail.html b/RIGS/templates/event_checklist_detail.html index 102f77c3..7ba7e23d 100644 --- a/RIGS/templates/event_checklist_detail.html +++ b/RIGS/templates/event_checklist_detail.html @@ -1,8 +1,6 @@ {% 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 title %}Event Checklist for Event N{{ object.event.pk|stringformat:"05d" }} {{ object.event.name }}{% endblock %} {% load help_text from filters %} -{% load get_json_element from filters %} -{% load get_item from filters %} {% load profile_by_index from filters %} {% block content %} @@ -24,11 +22,10 @@ {{ object.power_mic.name }} -

{{ object|help_text:'vehicles' }}

+

List vehicles and their drivers

    - {% for i in object.vehicles %} - -
  • Vehicle {{ object.vehicles|get_item:i|get_item:'vehicle'|default:'none' }} driven by {{ object.vehicles|get_item:i|get_item:'driver'|profile_by_index|default:'nobody'}}
  • + {% for i in object.vehicles.all %} +
  • {{i}}
  • {% endfor %}
diff --git a/RIGS/templates/event_checklist_form.html b/RIGS/templates/event_checklist_form.html index 6ae3b8d8..f7fc254b 100644 --- a/RIGS/templates/event_checklist_form.html +++ b/RIGS/templates/event_checklist_form.html @@ -2,8 +2,6 @@ {% load widget_tweaks %} {% load static %} {% load help_text from filters %} -{% load get_json_element from filters %} -{% load get_item from filters %} {% load profile_by_index from filters %} {% block title %}{% if edit %}Edit{% else %}Create{% endif %} Event Checklist for Event N{{ event.pk|stringformat:"05d" }}{% endblock %} @@ -55,39 +53,31 @@ $('#medium-event').slideUp(); } }); - $("form").submit(function( event ) { - // Mmmm Javascript data mangling... - var raw = $('*[data-serialize]').serializeArray(); - var post = raw.reduce(function (result, current) { - var index = current.name.split('_')[1]; - var name = current.name.split('_')[0]; - result[index] = result[index] || {}; - var nested = result[index] || {}; - if(current.value): - nested[name] = current.value; - result[index] = nested; - return result; - }, {}); - $({{form.vehicles.id_for_label}}).val(JSON.stringify(post)); - }); $('#vehicle-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; - }).attr('data-serialize', 'true'); + //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'); $(".selectpicker").each(function(){initPicker($(this))}); - $('#vehiclest').attr('data-pk', newID + 1); + $('#vehiclest').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(); }); }); {% endblock %} {% block content %} -
+

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

{% include 'form_errors.html' %} {% if edit %} @@ -122,36 +112,36 @@ {% endif %}
- - +

List vehicles and their drivers

+ - + - + + {# TODO Add required to all fields on row when one is edited #} - {% for i in vehicles_length %} - - + {% for i in object.vehicles.all %} + + - {# TODO Delete functionality {% endfor %} diff --git a/RIGS/templates/hs_list.html b/RIGS/templates/hs_list.html index e59b6490..99940bd3 100644 --- a/RIGS/templates/hs_list.html +++ b/RIGS/templates/hs_list.html @@ -39,8 +39,12 @@ {% endif %} {% empty %} diff --git a/RIGS/templates/invoice_detail.html b/RIGS/templates/invoice_detail.html index 4824326a..724427e5 100644 --- a/RIGS/templates/invoice_detail.html +++ b/RIGS/templates/invoice_detail.html @@ -53,13 +53,13 @@ {% endif %} -
+
-
+
Add @@ -104,7 +104,9 @@
- {% include 'partials/last_edited.html' with target="invoice_history" %} +
+ {% include 'partials/last_edited.html' with target="invoice_history" %} +
{% endblock %} diff --git a/RIGS/templatetags/filters.py b/RIGS/templatetags/filters.py index f95ff0f6..2c0af443 100644 --- a/RIGS/templatetags/filters.py +++ b/RIGS/templatetags/filters.py @@ -137,19 +137,6 @@ def profile_by_index(value): else: return "" -#TODO More sensible returns -@register.filter -def get_json_element(value, element): - try: - return json.loads(value)[str(element)] - except Exception as e: - return None - -@register.filter -def get_item(dictionary, key): - if(type(dictionary) is dict): - return dictionary.get(key) - @register.filter def next(alist, current_index): """ diff --git a/RIGS/urls.py b/RIGS/urls.py index a1436fae..439b8cee 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -115,6 +115,9 @@ urlpatterns = [ name='ec_history', kwargs={'model': models.EventChecklist}), path('event/checklist/list', permission_required_with_403('RIGS.change_event')(hs.EventChecklistList.as_view()), name='ec_list'), + # TEMPORARY + path('event/vehicle//history/', permission_required_with_403('RIGS.change_event')(versioning.VersionHistory.as_view()), + name='vc_history', kwargs={'model': models.EventChecklistVehicle}), # Finance path('invoice/', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()), diff --git a/versioning/templates/activity_feed_data.html b/versioning/templates/activity_feed_data.html index 6f7e0fad..22f5410f 100644 --- a/versioning/templates/activity_feed_data.html +++ b/versioning/templates/activity_feed_data.html @@ -35,7 +35,7 @@ {% if version.changes.old == None %} Created {% else %} - Changed {% include 'version_changes.html' %} in + Changed {% include 'partials/version_changes.html' %} in {% endif %} {% include 'partials/object_button.html' with object=version.changes.new %} diff --git a/versioning/templates/version_changes.html b/versioning/templates/partials/version_changes.html similarity index 92% rename from versioning/templates/version_changes.html rename to versioning/templates/partials/version_changes.html index 541b33c0..a3a5a081 100644 --- a/versioning/templates/version_changes.html +++ b/versioning/templates/partials/version_changes.html @@ -1,4 +1,4 @@ -{% if version.changes.item_changes or version.changes.field_changes or version.changes.old == None %} +{% if version.changes.anything_changed or version.changes.old == None %} {% for change in version.changes.field_changes %} {{ change.field.verbose_name }} {% endfor %} diff --git a/versioning/templates/partials/version_history_table.html b/versioning/templates/partials/version_history_table.html index 96fe6780..f45c099e 100644 --- a/versioning/templates/partials/version_history_table.html +++ b/versioning/templates/partials/version_history_table.html @@ -19,7 +19,7 @@ {% if version.changes.old is None %} {{object|to_class_name}} Created {% else %} - {% include 'version_changes.html' %} + {% include 'partials/version_changes.html' %} {% endif %} diff --git a/versioning/versioning.py b/versioning/versioning.py index 93b6bd15..85f727d2 100644 --- a/versioning/versioning.py +++ b/versioning/versioning.py @@ -67,7 +67,6 @@ class FieldComparison(object): class ModelComparison(object): - def __init__(self, old=None, new=None, version=None, excluded_keys=[]): # recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects try: @@ -117,29 +116,30 @@ 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) - 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_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) - comparisonParams = {'excluded_keys': ['id', 'event', 'order']} + comparisonParams = {'excluded_keys': ['id', 'event', 'order']} - # Build some dicts of what we have - 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 - 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 + # 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 - 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 + item_dict[version.object_id] = compare # update the dictionary with the changes changes = [] for (_, compare) in list(item_dict.items()):
Vehicle Driver
- + {% if i.driver != '' %} + + {% endif %}
- + {% if event.eventchecklist %} + View + {% else %} + + {% endif %}