From 46434977fb4b3f186f0044417f878cb59e890f26 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 5 Apr 2016 04:18:19 +0100 Subject: [PATCH 001/120] Created merge admin action for Person, Venue and Organisation models. Added template. --- RIGS/admin.py | 46 +++++++++++++++++-- .../templates/RIGS/admin_associate_merge.html | 21 +++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 RIGS/templates/RIGS/admin_associate_merge.html diff --git a/RIGS/admin.py b/RIGS/admin.py index 18dc554e..a72c4705 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -4,16 +4,20 @@ from django.contrib.auth.admin import UserAdmin from django.utils.translation import ugettext_lazy as _ import reversion +from django.contrib.admin import helpers +from django.template.response import TemplateResponse +from django.contrib import messages +from django.db import transaction +from django.core.exceptions import ObjectDoesNotExist + # Register your models here. -admin.site.register(models.Person, reversion.VersionAdmin) -admin.site.register(models.Organisation, reversion.VersionAdmin) admin.site.register(models.VatRate, reversion.VersionAdmin) -admin.site.register(models.Venue, reversion.VersionAdmin) admin.site.register(models.Event, reversion.VersionAdmin) admin.site.register(models.EventItem, reversion.VersionAdmin) admin.site.register(models.Invoice) admin.site.register(models.Payment) +@admin.register(models.Profile) class ProfileAdmin(UserAdmin): fieldsets = ( (None, {'fields': ('username', 'password')}), @@ -33,4 +37,38 @@ class ProfileAdmin(UserAdmin): form = forms.ProfileChangeForm add_form = forms.ProfileCreationForm -admin.site.register(models.Profile, ProfileAdmin) +@admin.register(models.Person, models.Organisation, models.Venue) +class AssociateAdmin(reversion.VersionAdmin): + list_display = ('id', 'name','number_of_events') + search_fields = ['id','name'] + + actions = ['merge'] + + def number_of_events(self,obj): + return obj.latest_events.count() + + def merge(self, request, queryset): + if request.POST.get('post'): # Has the user confirmed which is the master record? + try: + masterObjectPk = request.POST.get('master') + masterObject = queryset.get(pk = masterObjectPk) + except ObjectDoesNotExist: + self.message_user(request, "An error occured. Did you select a 'master' record?",level=messages.ERROR) + return + + with transaction.atomic(), reversion.create_revision(): + for obj in queryset.exclude(pk = masterObjectPk): + events = obj.event_set.all() + for event in events: + masterObject.event_set.add(event) + obj.delete() + + self.message_user(request, "Objects successfully merged.") + return + else: # Present the confirmation screen + context = { + 'title': _("Are you sure?"), + 'queryset': queryset, + 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, + } + return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context, current_app=self.admin_site.name) \ No newline at end of file diff --git a/RIGS/templates/RIGS/admin_associate_merge.html b/RIGS/templates/RIGS/admin_associate_merge.html new file mode 100644 index 00000000..e30b2e55 --- /dev/null +++ b/RIGS/templates/RIGS/admin_associate_merge.html @@ -0,0 +1,21 @@ +{% extends "admin/base_site.html" %} +{% load i18n l10n %} + +{% block content %} +
{% csrf_token %} +

The following objects will be merged. Please select the 'master' record which you would like to keep. Other records will have associated events moved to the 'master' copy, and then will be deleted.

+ + {% for item in queryset %} + {{item.pk}} | {{item}}
+ {% endfor %} + +
+ {% for obj in queryset %} + + {% endfor %} + + + +
+
+{% endblock %} \ No newline at end of file From 33ce4b622d3a8e034178f0bee8a10d0f72caea3d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 5 Apr 2016 04:18:53 +0100 Subject: [PATCH 002/120] Fixed bug with versioning interface when related objects are deleted --- RIGS/versioning.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 789fc5d7..c537de87 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -92,9 +92,16 @@ def model_compare(oldObj, newObj, excluded_keys=[]): if name in excluded_keys: continue # if we're excluding this field, skip over it - oldValue = getattr(oldObj, name, None) - newValue = getattr(newObj, name, None) + try: + oldValue = getattr(oldObj, name, None) + except ObjectDoesNotExist: + oldValue = None + try: + newValue = getattr(newObj, name, None) + except ObjectDoesNotExist: + newValue = None + try: bothBlank = (not oldValue) and (not newValue) if oldValue != newValue and not bothBlank: From ca6cddb3924bd185532a716ebb12aa9b79f951b7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 5 Apr 2016 11:50:34 +0100 Subject: [PATCH 003/120] Add comments display to versioning history (because why not). Maybe in future we could have a box people can type in before they save changes to an event... But that's a separate project --- RIGS/admin.py | 3 ++- RIGS/templates/RIGS/activity_feed_data.html | 3 +++ RIGS/templates/RIGS/activity_table.html | 2 ++ RIGS/templates/RIGS/version_history.html | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/RIGS/admin.py b/RIGS/admin.py index a72c4705..d8583e23 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -62,7 +62,8 @@ class AssociateAdmin(reversion.VersionAdmin): for event in events: masterObject.event_set.add(event) obj.delete() - + reversion.set_comment('Merging Objects') + self.message_user(request, "Objects successfully merged.") return else: # Present the confirmation screen diff --git a/RIGS/templates/RIGS/activity_feed_data.html b/RIGS/templates/RIGS/activity_feed_data.html index fc9af87e..e99baf8e 100644 --- a/RIGS/templates/RIGS/activity_feed_data.html +++ b/RIGS/templates/RIGS/activity_feed_data.html @@ -40,6 +40,9 @@ {% endif %} {% include 'RIGS/object_button.html' with object=version.new %} + {% if version.revision.comment %} + ({{ version.revision.comment }}) + {% endif %}

diff --git a/RIGS/templates/RIGS/activity_table.html b/RIGS/templates/RIGS/activity_table.html index bf625e44..1d491663 100644 --- a/RIGS/templates/RIGS/activity_table.html +++ b/RIGS/templates/RIGS/activity_table.html @@ -59,6 +59,7 @@ Version ID User Changes + Comment @@ -75,6 +76,7 @@ {% else %} {% include 'RIGS/version_changes.html' %} {% endif %} + {{ version.revision.comment }} {% endfor %} diff --git a/RIGS/templates/RIGS/version_history.html b/RIGS/templates/RIGS/version_history.html index 163a1ac5..924ea148 100644 --- a/RIGS/templates/RIGS/version_history.html +++ b/RIGS/templates/RIGS/version_history.html @@ -35,6 +35,7 @@ Version ID User Changes + Comment @@ -51,6 +52,9 @@ {% include 'RIGS/version_changes.html' %} {% endif %} + + {{ version.revision.comment }} + {% endif %} {% endfor %} From 03ca65602fd2e41a6556473820fa493c774e97c6 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 5 Apr 2016 12:08:19 +0100 Subject: [PATCH 004/120] Allow sorting by number of events --- RIGS/admin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RIGS/admin.py b/RIGS/admin.py index d8583e23..9bfcb3eb 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -9,6 +9,7 @@ from django.template.response import TemplateResponse from django.contrib import messages from django.db import transaction from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Count # Register your models here. admin.site.register(models.VatRate, reversion.VersionAdmin) @@ -44,9 +45,14 @@ class AssociateAdmin(reversion.VersionAdmin): actions = ['merge'] + def get_queryset(self, request): + return super(AssociateAdmin, self).get_queryset(request).annotate(event_count = Count('event')) + def number_of_events(self,obj): return obj.latest_events.count() + number_of_events.admin_order_field = 'event_count' + def merge(self, request, queryset): if request.POST.get('post'): # Has the user confirmed which is the master record? try: From 99dfdcd253a094f3eb4c5613f600024a60ef72a6 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 5 Apr 2016 12:53:04 +0100 Subject: [PATCH 005/120] Make confirmation more useful --- RIGS/admin.py | 35 ++++++++++++++++--- .../templates/RIGS/admin_associate_merge.html | 25 +++++++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/RIGS/admin.py b/RIGS/admin.py index 9bfcb3eb..0dfafd7a 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -10,6 +10,7 @@ from django.contrib import messages from django.db import transaction from django.core.exceptions import ObjectDoesNotExist from django.db.models import Count +from django.forms import ModelForm # Register your models here. admin.site.register(models.VatRate, reversion.VersionAdmin) @@ -38,19 +39,19 @@ class ProfileAdmin(UserAdmin): form = forms.ProfileChangeForm add_form = forms.ProfileCreationForm -@admin.register(models.Person, models.Organisation, models.Venue) class AssociateAdmin(reversion.VersionAdmin): list_display = ('id', 'name','number_of_events') search_fields = ['id','name'] - + list_display_links = ['id','name'] actions = ['merge'] + merge_fields = ['name'] + def get_queryset(self, request): return super(AssociateAdmin, self).get_queryset(request).annotate(event_count = Count('event')) def number_of_events(self,obj): return obj.latest_events.count() - number_of_events.admin_order_field = 'event_count' def merge(self, request, queryset): @@ -73,9 +74,35 @@ class AssociateAdmin(reversion.VersionAdmin): self.message_user(request, "Objects successfully merged.") return else: # Present the confirmation screen + + class TempForm(ModelForm): + class Meta: + model = queryset.model + fields = self.merge_fields + + forms = [] + for obj in queryset: + forms.append(TempForm(instance=obj)) + context = { 'title': _("Are you sure?"), 'queryset': queryset, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, + 'forms': forms } - return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context, current_app=self.admin_site.name) \ No newline at end of file + return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context, current_app=self.admin_site.name) + +@admin.register(models.Person) +class PersonAdmin(AssociateAdmin): + list_display = ('id', 'name','phone','email','number_of_events') + merge_fields = ['name','phone','email','address','notes'] + +@admin.register(models.Venue) +class VenueAdmin(AssociateAdmin): + list_display = ('id', 'name','phone','email','number_of_events') + merge_fields = ['name','phone','email','address','notes','three_phase_available'] + +@admin.register(models.Organisation) +class OrganisationAdmin(AssociateAdmin): + list_display = ('id', 'name','phone','email','number_of_events') + merge_fields = ['name','phone','email','address','notes','union_account'] \ No newline at end of file diff --git a/RIGS/templates/RIGS/admin_associate_merge.html b/RIGS/templates/RIGS/admin_associate_merge.html index e30b2e55..2128725c 100644 --- a/RIGS/templates/RIGS/admin_associate_merge.html +++ b/RIGS/templates/RIGS/admin_associate_merge.html @@ -5,13 +5,32 @@
{% csrf_token %}

The following objects will be merged. Please select the 'master' record which you would like to keep. Other records will have associated events moved to the 'master' copy, and then will be deleted.

- {% for item in queryset %} - {{item.pk}} | {{item}}
+ + {% for form in forms %} + {% if forloop.first %} + + + + {% for field in form %} + + {% endfor %} + + {% endif %} + + + + + {% for field in form %} + + {% endfor %} + {% endfor %} +
ID {{ field.label }}
{{form.instance.pk}} {{ field.value }}
+
{% for obj in queryset %} - + {% endfor %} From 44ccead0a46daea308a8657815170867f24bb43d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 5 Apr 2016 15:32:13 +0100 Subject: [PATCH 006/120] Added merge tests I feel like it should be possible to abstract some of these so that they're not copied out for each model, but not sure how to go about it... --- RIGS/test_unit.py | 139 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 RIGS/test_unit.py diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py new file mode 100644 index 00000000..b6fc8edd --- /dev/null +++ b/RIGS/test_unit.py @@ -0,0 +1,139 @@ +from django.core.urlresolvers import reverse +from django.test import TestCase +from datetime import date + +from RIGS import models +from django.core.exceptions import ObjectDoesNotExist + +class TestAdminMergeObjects(TestCase): + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + + cls.persons = { + 1: models.Person.objects.create(name="Person 1"), + 2: models.Person.objects.create(name="Person 2"), + 3: models.Person.objects.create(name="Person 3"), + } + + cls.organisations = { + 1: models.Organisation.objects.create(name="Organisation 1"), + 2: models.Organisation.objects.create(name="Organisation 2"), + 3: models.Organisation.objects.create(name="Organisation 3"), + } + + cls.venues = { + 1: models.Venue.objects.create(name="Venue 1"), + 2: models.Venue.objects.create(name="Venue 2"), + 3: models.Venue.objects.create(name="Venue 3"), + } + + cls.events = { + 1: models.Event.objects.create(name="TE E1", start_date=date.today(), person=cls.persons[1], organisation=cls.organisations[3], venue=cls.venues[2]), + 2: models.Event.objects.create(name="TE E2", start_date=date.today(), person=cls.persons[2], organisation=cls.organisations[2], venue=cls.venues[3]), + 3: models.Event.objects.create(name="TE E3", start_date=date.today(), person=cls.persons[3], organisation=cls.organisations[1], venue=cls.venues[1]), + 4: models.Event.objects.create(name="TE E4", start_date=date.today(), person=cls.persons[3], organisation=cls.organisations[3], venue=cls.venues[3]), + } + + def setUp(self): + self.profile.set_password('testuser') + self.profile.save() + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + + def test_merge_confirmation(self): + change_url = reverse('admin:RIGS_venue_changelist') + data = { + 'action': 'merge', + '_selected_action': [unicode(val.pk) for key,val in self.venues.iteritems()] + + } + response = self.client.post(change_url, data, follow=True) + + self.assertContains(response, "The following objects will be merged") + for key,venue in self.venues.iteritems(): + self.assertContains(response, venue.name) + + def test_merge_no_master(self): + change_url = reverse('admin:RIGS_venue_changelist') + data = {'action': 'merge', + '_selected_action': [unicode(val.pk) for key,val in self.venues.iteritems()], + 'post':'yes', + } + response = self.client.post(change_url, data, follow=True) + + self.assertContains(response, "An error occured") + + def test_venue_merge(self): + change_url = reverse('admin:RIGS_venue_changelist') + + data = {'action': 'merge', + '_selected_action': [unicode(val.pk) for key,val in self.venues.iteritems()], + 'post':'yes', + 'master':self.venues[1].pk + } + + response = self.client.post(change_url, data, follow=True) + self.assertContains(response, "Objects successfully merged") + self.assertContains(response, self.venues[1].name) + + # Check the master copy still exists + self.assertTrue(models.Venue.objects.get(pk=self.venues[1].pk)) + + # Check the un-needed venues have been disposed of + self.assertRaises(ObjectDoesNotExist, models.Venue.objects.get, pk=self.venues[2].pk) + self.assertRaises(ObjectDoesNotExist, models.Venue.objects.get, pk=self.venues[3].pk) + + # Check the events have been moved to the master venue + for key,event in self.events.iteritems(): + updatedEvent = models.Event.objects.get(pk=event.pk) + self.assertEqual(updatedEvent.venue, self.venues[1]) + + def test_person_merge(self): + change_url = reverse('admin:RIGS_person_changelist') + + data = {'action': 'merge', + '_selected_action': [unicode(val.pk) for key,val in self.persons.iteritems()], + 'post':'yes', + 'master':self.persons[1].pk + } + + response = self.client.post(change_url, data, follow=True) + self.assertContains(response, "Objects successfully merged") + self.assertContains(response, self.persons[1].name) + + # Check the master copy still exists + self.assertTrue(models.Person.objects.get(pk=self.persons[1].pk)) + + # Check the un-needed people have been disposed of + self.assertRaises(ObjectDoesNotExist, models.Person.objects.get, pk=self.persons[2].pk) + self.assertRaises(ObjectDoesNotExist, models.Person.objects.get, pk=self.persons[3].pk) + + # Check the events have been moved to the master person + for key,event in self.events.iteritems(): + updatedEvent = models.Event.objects.get(pk=event.pk) + self.assertEqual(updatedEvent.person, self.persons[1]) + + def test_organisation_merge(self): + change_url = reverse('admin:RIGS_organisation_changelist') + + data = {'action': 'merge', + '_selected_action': [unicode(val.pk) for key,val in self.organisations.iteritems()], + 'post':'yes', + 'master':self.organisations[1].pk + } + + response = self.client.post(change_url, data, follow=True) + self.assertContains(response, "Objects successfully merged") + self.assertContains(response, self.organisations[1].name) + + # Check the master copy still exists + self.assertTrue(models.Organisation.objects.get(pk=self.organisations[1].pk)) + + # Check the un-needed organisations have been disposed of + self.assertRaises(ObjectDoesNotExist, models.Organisation.objects.get, pk=self.organisations[2].pk) + self.assertRaises(ObjectDoesNotExist, models.Organisation.objects.get, pk=self.organisations[3].pk) + + # Check the events have been moved to the master organisation + for key,event in self.events.iteritems(): + updatedEvent = models.Event.objects.get(pk=event.pk) + self.assertEqual(updatedEvent.organisation, self.organisations[1]) \ No newline at end of file From ebe08c3bf1dc7b3dd9411c0de691bee9a91468eb Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 6 Apr 2016 21:52:25 +0100 Subject: [PATCH 007/120] Update tests to check items that aren't selected aren't affected. PEP8 format the file --- RIGS/test_unit.py | 226 +++++++++++++++++++++++++--------------------- 1 file changed, 122 insertions(+), 104 deletions(-) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index b6fc8edd..75fcca5a 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -5,135 +5,153 @@ from datetime import date from RIGS import models from django.core.exceptions import ObjectDoesNotExist + class TestAdminMergeObjects(TestCase): - @classmethod - def setUpTestData(cls): - cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, + is_active=True, is_staff=True) - cls.persons = { - 1: models.Person.objects.create(name="Person 1"), - 2: models.Person.objects.create(name="Person 2"), - 3: models.Person.objects.create(name="Person 3"), - } + cls.persons = { + 1: models.Person.objects.create(name="Person 1"), + 2: models.Person.objects.create(name="Person 2"), + 3: models.Person.objects.create(name="Person 3"), + } - cls.organisations = { - 1: models.Organisation.objects.create(name="Organisation 1"), - 2: models.Organisation.objects.create(name="Organisation 2"), - 3: models.Organisation.objects.create(name="Organisation 3"), - } + cls.organisations = { + 1: models.Organisation.objects.create(name="Organisation 1"), + 2: models.Organisation.objects.create(name="Organisation 2"), + 3: models.Organisation.objects.create(name="Organisation 3"), + } - cls.venues = { - 1: models.Venue.objects.create(name="Venue 1"), - 2: models.Venue.objects.create(name="Venue 2"), - 3: models.Venue.objects.create(name="Venue 3"), - } + cls.venues = { + 1: models.Venue.objects.create(name="Venue 1"), + 2: models.Venue.objects.create(name="Venue 2"), + 3: models.Venue.objects.create(name="Venue 3"), + } - cls.events = { - 1: models.Event.objects.create(name="TE E1", start_date=date.today(), person=cls.persons[1], organisation=cls.organisations[3], venue=cls.venues[2]), - 2: models.Event.objects.create(name="TE E2", start_date=date.today(), person=cls.persons[2], organisation=cls.organisations[2], venue=cls.venues[3]), - 3: models.Event.objects.create(name="TE E3", start_date=date.today(), person=cls.persons[3], organisation=cls.organisations[1], venue=cls.venues[1]), - 4: models.Event.objects.create(name="TE E4", start_date=date.today(), person=cls.persons[3], organisation=cls.organisations[3], venue=cls.venues[3]), - } + cls.events = { + 1: models.Event.objects.create(name="TE E1", start_date=date.today(), person=cls.persons[1], + organisation=cls.organisations[3], venue=cls.venues[2]), + 2: models.Event.objects.create(name="TE E2", start_date=date.today(), person=cls.persons[2], + organisation=cls.organisations[2], venue=cls.venues[3]), + 3: models.Event.objects.create(name="TE E3", start_date=date.today(), person=cls.persons[3], + organisation=cls.organisations[1], venue=cls.venues[1]), + 4: models.Event.objects.create(name="TE E4", start_date=date.today(), person=cls.persons[3], + organisation=cls.organisations[3], venue=cls.venues[3]), + } - def setUp(self): - self.profile.set_password('testuser') - self.profile.save() - self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + def setUp(self): + self.profile.set_password('testuser') + self.profile.save() + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) - def test_merge_confirmation(self): - change_url = reverse('admin:RIGS_venue_changelist') - data = { - 'action': 'merge', - '_selected_action': [unicode(val.pk) for key,val in self.venues.iteritems()] + def test_merge_confirmation(self): + change_url = reverse('admin:RIGS_venue_changelist') + data = { + 'action': 'merge', + '_selected_action': [unicode(val.pk) for key, val in self.venues.iteritems()] - } - response = self.client.post(change_url, data, follow=True) + } + response = self.client.post(change_url, data, follow=True) - self.assertContains(response, "The following objects will be merged") - for key,venue in self.venues.iteritems(): - self.assertContains(response, venue.name) + self.assertContains(response, "The following objects will be merged") + for key, venue in self.venues.iteritems(): + self.assertContains(response, venue.name) - def test_merge_no_master(self): - change_url = reverse('admin:RIGS_venue_changelist') - data = {'action': 'merge', - '_selected_action': [unicode(val.pk) for key,val in self.venues.iteritems()], - 'post':'yes', - } - response = self.client.post(change_url, data, follow=True) + def test_merge_no_master(self): + change_url = reverse('admin:RIGS_venue_changelist') + data = {'action': 'merge', + '_selected_action': [unicode(val.pk) for key, val in self.venues.iteritems()], + 'post': 'yes', + } + response = self.client.post(change_url, data, follow=True) - self.assertContains(response, "An error occured") - - def test_venue_merge(self): - change_url = reverse('admin:RIGS_venue_changelist') + self.assertContains(response, "An error occured") - data = {'action': 'merge', - '_selected_action': [unicode(val.pk) for key,val in self.venues.iteritems()], - 'post':'yes', - 'master':self.venues[1].pk - } + def test_venue_merge(self): + change_url = reverse('admin:RIGS_venue_changelist') - response = self.client.post(change_url, data, follow=True) - self.assertContains(response, "Objects successfully merged") - self.assertContains(response, self.venues[1].name) + data = {'action': 'merge', + '_selected_action': [unicode(self.venues[1].pk), unicode(self.venues[2].pk)], + 'post': 'yes', + 'master': self.venues[1].pk + } - # Check the master copy still exists - self.assertTrue(models.Venue.objects.get(pk=self.venues[1].pk)) + response = self.client.post(change_url, data, follow=True) + self.assertContains(response, "Objects successfully merged") + self.assertContains(response, self.venues[1].name) - # Check the un-needed venues have been disposed of - self.assertRaises(ObjectDoesNotExist, models.Venue.objects.get, pk=self.venues[2].pk) - self.assertRaises(ObjectDoesNotExist, models.Venue.objects.get, pk=self.venues[3].pk) + # Check the master copy still exists + self.assertTrue(models.Venue.objects.get(pk=self.venues[1].pk)) - # Check the events have been moved to the master venue - for key,event in self.events.iteritems(): - updatedEvent = models.Event.objects.get(pk=event.pk) - self.assertEqual(updatedEvent.venue, self.venues[1]) + # Check the un-needed venue has been disposed of + self.assertRaises(ObjectDoesNotExist, models.Venue.objects.get, pk=self.venues[2].pk) - def test_person_merge(self): - change_url = reverse('admin:RIGS_person_changelist') + # Check the one we didn't delete is still there + self.assertEqual(models.Venue.objects.get(pk=self.venues[3].pk), self.venues[3]) - data = {'action': 'merge', - '_selected_action': [unicode(val.pk) for key,val in self.persons.iteritems()], - 'post':'yes', - 'master':self.persons[1].pk - } + # Check the events have been moved to the master venue + for key, event in self.events.iteritems(): + updatedEvent = models.Event.objects.get(pk=event.pk) + if event.venue == self.venues[3]: # The one we left in place + continue + self.assertEqual(updatedEvent.venue, self.venues[1]) - response = self.client.post(change_url, data, follow=True) - self.assertContains(response, "Objects successfully merged") - self.assertContains(response, self.persons[1].name) + def test_person_merge(self): + change_url = reverse('admin:RIGS_person_changelist') - # Check the master copy still exists - self.assertTrue(models.Person.objects.get(pk=self.persons[1].pk)) + data = {'action': 'merge', + '_selected_action': [unicode(self.persons[1].pk), unicode(self.persons[2].pk)], + 'post': 'yes', + 'master': self.persons[1].pk + } - # Check the un-needed people have been disposed of - self.assertRaises(ObjectDoesNotExist, models.Person.objects.get, pk=self.persons[2].pk) - self.assertRaises(ObjectDoesNotExist, models.Person.objects.get, pk=self.persons[3].pk) + response = self.client.post(change_url, data, follow=True) + self.assertContains(response, "Objects successfully merged") + self.assertContains(response, self.persons[1].name) - # Check the events have been moved to the master person - for key,event in self.events.iteritems(): - updatedEvent = models.Event.objects.get(pk=event.pk) - self.assertEqual(updatedEvent.person, self.persons[1]) + # Check the master copy still exists + self.assertTrue(models.Person.objects.get(pk=self.persons[1].pk)) - def test_organisation_merge(self): - change_url = reverse('admin:RIGS_organisation_changelist') + # Check the un-needed people have been disposed of + self.assertRaises(ObjectDoesNotExist, models.Person.objects.get, pk=self.persons[2].pk) - data = {'action': 'merge', - '_selected_action': [unicode(val.pk) for key,val in self.organisations.iteritems()], - 'post':'yes', - 'master':self.organisations[1].pk - } + # Check the one we didn't delete is still there + self.assertEqual(models.Person.objects.get(pk=self.persons[3].pk), self.persons[3]) - response = self.client.post(change_url, data, follow=True) - self.assertContains(response, "Objects successfully merged") - self.assertContains(response, self.organisations[1].name) + # Check the events have been moved to the master person + for key, event in self.events.iteritems(): + updatedEvent = models.Event.objects.get(pk=event.pk) + if event.person == self.persons[3]: # The one we left in place + continue + self.assertEqual(updatedEvent.person, self.persons[1]) - # Check the master copy still exists - self.assertTrue(models.Organisation.objects.get(pk=self.organisations[1].pk)) + def test_organisation_merge(self): + change_url = reverse('admin:RIGS_organisation_changelist') - # Check the un-needed organisations have been disposed of - self.assertRaises(ObjectDoesNotExist, models.Organisation.objects.get, pk=self.organisations[2].pk) - self.assertRaises(ObjectDoesNotExist, models.Organisation.objects.get, pk=self.organisations[3].pk) + data = {'action': 'merge', + '_selected_action': [unicode(self.organisations[1].pk), unicode(self.organisations[2].pk)], + 'post': 'yes', + 'master': self.organisations[1].pk + } - # Check the events have been moved to the master organisation - for key,event in self.events.iteritems(): - updatedEvent = models.Event.objects.get(pk=event.pk) - self.assertEqual(updatedEvent.organisation, self.organisations[1]) \ No newline at end of file + response = self.client.post(change_url, data, follow=True) + self.assertContains(response, "Objects successfully merged") + self.assertContains(response, self.organisations[1].name) + + # Check the master copy still exists + self.assertTrue(models.Organisation.objects.get(pk=self.organisations[1].pk)) + + # Check the un-needed organisations have been disposed of + self.assertRaises(ObjectDoesNotExist, models.Organisation.objects.get, pk=self.organisations[2].pk) + + # Check the one we didn't delete is still there + self.assertEqual(models.Organisation.objects.get(pk=self.organisations[3].pk), self.organisations[3]) + + # Check the events have been moved to the master organisation + for key, event in self.events.iteritems(): + updatedEvent = models.Event.objects.get(pk=event.pk) + if event.organisation == self.organisations[3]: # The one we left in place + continue + self.assertEqual(updatedEvent.organisation, self.organisations[1]) From 823db68a6a534e6f445f456c4893ff83cefaf742 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 6 Apr 2016 21:53:38 +0100 Subject: [PATCH 008/120] PEP8 format files --- RIGS/admin.py | 49 ++++++++++-------- RIGS/versioning.py | 122 ++++++++++++++++++++++++--------------------- 2 files changed, 92 insertions(+), 79 deletions(-) diff --git a/RIGS/admin.py b/RIGS/admin.py index 0dfafd7a..a351aed0 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -6,7 +6,7 @@ import reversion from django.contrib.admin import helpers from django.template.response import TemplateResponse -from django.contrib import messages +from django.contrib import messages from django.db import transaction from django.core.exceptions import ObjectDoesNotExist from django.db.models import Count @@ -19,16 +19,17 @@ admin.site.register(models.EventItem, reversion.VersionAdmin) admin.site.register(models.Invoice) admin.site.register(models.Payment) + @admin.register(models.Profile) class ProfileAdmin(UserAdmin): fieldsets = ( (None, {'fields': ('username', 'password')}), (_('Personal info'), { - 'fields': ('first_name', 'last_name', 'email', 'initials', 'phone')}), + 'fields': ('first_name', 'last_name', 'email', 'initials', 'phone')}), (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), (_('Important dates'), { - 'fields': ('last_login', 'date_joined')}), + 'fields': ('last_login', 'date_joined')}), ) add_fieldsets = ( (None, { @@ -39,41 +40,43 @@ class ProfileAdmin(UserAdmin): form = forms.ProfileChangeForm add_form = forms.ProfileCreationForm + class AssociateAdmin(reversion.VersionAdmin): - list_display = ('id', 'name','number_of_events') - search_fields = ['id','name'] - list_display_links = ['id','name'] + list_display = ('id', 'name', 'number_of_events') + search_fields = ['id', 'name'] + list_display_links = ['id', 'name'] actions = ['merge'] merge_fields = ['name'] def get_queryset(self, request): - return super(AssociateAdmin, self).get_queryset(request).annotate(event_count = Count('event')) + return super(AssociateAdmin, self).get_queryset(request).annotate(event_count=Count('event')) - def number_of_events(self,obj): + def number_of_events(self, obj): return obj.latest_events.count() + number_of_events.admin_order_field = 'event_count' def merge(self, request, queryset): - if request.POST.get('post'): # Has the user confirmed which is the master record? + if request.POST.get('post'): # Has the user confirmed which is the master record? try: masterObjectPk = request.POST.get('master') - masterObject = queryset.get(pk = masterObjectPk) + masterObject = queryset.get(pk=masterObjectPk) except ObjectDoesNotExist: - self.message_user(request, "An error occured. Did you select a 'master' record?",level=messages.ERROR) + self.message_user(request, "An error occured. Did you select a 'master' record?", level=messages.ERROR) return with transaction.atomic(), reversion.create_revision(): - for obj in queryset.exclude(pk = masterObjectPk): + for obj in queryset.exclude(pk=masterObjectPk): events = obj.event_set.all() for event in events: masterObject.event_set.add(event) - obj.delete() + obj.delete() reversion.set_comment('Merging Objects') self.message_user(request, "Objects successfully merged.") return - else: # Present the confirmation screen + else: # Present the confirmation screen class TempForm(ModelForm): class Meta: @@ -90,19 +93,23 @@ class AssociateAdmin(reversion.VersionAdmin): 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'forms': forms } - return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context, current_app=self.admin_site.name) + return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context, + current_app=self.admin_site.name) + @admin.register(models.Person) class PersonAdmin(AssociateAdmin): - list_display = ('id', 'name','phone','email','number_of_events') - merge_fields = ['name','phone','email','address','notes'] + list_display = ('id', 'name', 'phone', 'email', 'number_of_events') + merge_fields = ['name', 'phone', 'email', 'address', 'notes'] + @admin.register(models.Venue) class VenueAdmin(AssociateAdmin): - list_display = ('id', 'name','phone','email','number_of_events') - merge_fields = ['name','phone','email','address','notes','three_phase_available'] + list_display = ('id', 'name', 'phone', 'email', 'number_of_events') + merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'three_phase_available'] + @admin.register(models.Organisation) class OrganisationAdmin(AssociateAdmin): - list_display = ('id', 'name','phone','email','number_of_events') - merge_fields = ['name','phone','email','address','notes','union_account'] \ No newline at end of file + list_display = ('id', 'name', 'phone', 'email', 'number_of_events') + merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'union_account'] diff --git a/RIGS/versioning.py b/RIGS/versioning.py index c537de87..36786a74 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -14,7 +14,7 @@ from django.core.exceptions import ObjectDoesNotExist import reversion import simplejson from reversion.models import Version -from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type +from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.db.models import ForeignKey, IntegerField, EmailField, TextField from diff_match_patch import diff_match_patch @@ -29,11 +29,10 @@ logger = logging.getLogger('tec.pyrigs') def model_compare(oldObj, newObj, excluded_keys=[]): # recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects try: - theFields = oldObj._meta.fields #This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an alternative becomes available) + theFields = oldObj._meta.fields # This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an alternative becomes available) except AttributeError: theFields = newObj._meta.fields - class FieldCompare(object): def __init__(self, field=None, old=None, new=None): self.field = field @@ -51,13 +50,13 @@ def model_compare(oldObj, newObj, excluded_keys=[]): @property def new(self): - return self.display_value(self._new) + return self.display_value(self._new) @property def long(self): if isinstance(self.field, EmailField): return True - return False + return False @property def linebreaks(self): @@ -76,21 +75,21 @@ def model_compare(oldObj, newObj, excluded_keys=[]): outputDiffs = [] for (op, data) in diffs: - if op == dmp.DIFF_INSERT: - outputDiffs.append({'type':'insert', 'text':data}) - elif op == dmp.DIFF_DELETE: - outputDiffs.append({'type':'delete', 'text':data}) - elif op == dmp.DIFF_EQUAL: - outputDiffs.append({'type':'equal', 'text':data}) + if op == dmp.DIFF_INSERT: + outputDiffs.append({'type': 'insert', 'text': data}) + elif op == dmp.DIFF_DELETE: + outputDiffs.append({'type': 'delete', 'text': data}) + elif op == dmp.DIFF_EQUAL: + outputDiffs.append({'type': 'equal', 'text': data}) return outputDiffs changes = [] for thisField in theFields: name = thisField.name - + if name in excluded_keys: - continue # if we're excluding this field, skip over it + continue # if we're excluding this field, skip over it try: oldValue = getattr(oldObj, name, None) @@ -101,17 +100,18 @@ def model_compare(oldObj, newObj, excluded_keys=[]): newValue = getattr(newObj, name, None) except ObjectDoesNotExist: newValue = None - + try: bothBlank = (not oldValue) and (not newValue) if oldValue != newValue and not bothBlank: - compare = FieldCompare(thisField,oldValue,newValue) + compare = FieldCompare(thisField, oldValue, newValue) changes.append(compare) - except TypeError: # logs issues with naive vs tz-aware datetimes + except TypeError: # logs issues with naive vs tz-aware datetimes logger.error('TypeError when comparing models') - + return changes + def compare_event_items(old, new): # Recieves two event version objects and compares their items, returns an array of ItemCompare objects @@ -126,39 +126,41 @@ def compare_event_items(old, new): self.changes = changes # 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 + 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 compare = ItemCompare(old=version.object_version.object) item_dict[version.object_id] = compare - for version in new_item_versions: # go through the new versions - 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 + for version in new_item_versions: # go through the new versions + 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 = ItemCompare(new=version.object_version.object) - - item_dict[version.object_id] = compare # update the dictionary with the changes - changes = [] + item_dict[version.object_id] = compare # update the dictionary with the changes + + changes = [] for (_, compare) in item_dict.items(): - compare.changes = model_compare(compare.old, compare.new, ['id','event','order']) # see what's changed + compare.changes = model_compare(compare.old, compare.new, ['id', 'event', 'order']) # see what's changed if len(compare.changes) >= 1: - changes.append(compare) # transfer into a sequential array to make it easier to deal with later + changes.append(compare) # transfer into a sequential array to make it easier to deal with later return changes + def get_versions_for_model(models): content_types = [] for model in models: content_types.append(ContentType.objects.get_for_model(model)) - + versions = reversion.models.Version.objects.filter( - content_type__in = content_types, + content_type__in=content_types, ).select_related("revision").order_by("-pk") return versions + def get_previous_version(version): thisId = version.object_id thisVersionId = version.pk @@ -166,17 +168,19 @@ def get_previous_version(version): versions = reversion.get_for_object_reference(version.content_type.model_class(), thisId) try: - previousVersions = versions.filter(revision_id__lt=version.revision_id).latest(field_name='revision__date_created') + previousVersions = versions.filter(revision_id__lt=version.revision_id).latest( + field_name='revision__date_created') except ObjectDoesNotExist: return False return previousVersions -def get_changes_for_version(newVersion, oldVersion=None): - #Pass in a previous version if you already know it (for efficiancy) - #if not provided then it will be looked up in the database - if oldVersion == None: +def get_changes_for_version(newVersion, oldVersion=None): + # Pass in a previous version if you already know it (for efficiancy) + # if not provided then it will be looked up in the database + + if oldVersion == None: oldVersion = get_previous_version(newVersion) modelClass = newVersion.content_type.model_class() @@ -200,6 +204,7 @@ def get_changes_for_version(newVersion, oldVersion=None): return compare + class VersionHistory(generic.ListView): model = reversion.revisions.Version template_name = "RIGS/version_history.html" @@ -215,7 +220,7 @@ class VersionHistory(generic.ListView): def get_context_data(self, **kwargs): thisModel = self.kwargs['model'] - + context = super(VersionHistory, self).get_context_data(**kwargs) versions = context['object_list'] @@ -224,81 +229,82 @@ class VersionHistory(generic.ListView): items = [] for versionNo, thisVersion in enumerate(versions): - if versionNo >= len(versions)-1: + if versionNo >= len(versions) - 1: thisItem = get_changes_for_version(thisVersion, None) else: - thisItem = get_changes_for_version(thisVersion, versions[versionNo+1]) - + thisItem = get_changes_for_version(thisVersion, versions[versionNo + 1]) + items.append(thisItem) context['object_list'] = items context['object'] = thisObject - + return context + class ActivityTable(generic.ListView): model = reversion.revisions.Version template_name = "RIGS/activity_table.html" paginate_by = 25 - + def get_queryset(self): - versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation]) + versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation]) return versions def get_context_data(self, **kwargs): - # Call the base implementation first to get a context context = super(ActivityTable, self).get_context_data(**kwargs) - + items = [] for thisVersion in context['object_list']: thisItem = get_changes_for_version(thisVersion, None) items.append(thisItem) - context ['object_list'] = items - + context['object_list'] = items + return context + class ActivityFeed(generic.ListView): model = reversion.revisions.Version template_name = "RIGS/activity_feed_data.html" paginate_by = 25 - + def get_queryset(self): - versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation]) + versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation]) return versions def get_context_data(self, **kwargs): maxTimeDelta = [] - maxTimeDelta.append({ 'maxAge':datetime.timedelta(days=1), 'group':datetime.timedelta(hours=1)}) - maxTimeDelta.append({ 'maxAge':None, 'group':datetime.timedelta(days=1)}) + maxTimeDelta.append({'maxAge': datetime.timedelta(days=1), 'group': datetime.timedelta(hours=1)}) + maxTimeDelta.append({'maxAge': None, 'group': datetime.timedelta(days=1)}) # Call the base implementation first to get a context context = super(ActivityFeed, self).get_context_data(**kwargs) - + items = [] for thisVersion in context['object_list']: thisItem = get_changes_for_version(thisVersion, None) if thisItem['item_changes'] or thisItem['field_changes'] or thisItem['old'] == None: thisItem['withPrevious'] = False - if len(items)>=1: - timeAgo = datetime.datetime.now(thisItem['revision'].date_created.tzinfo) - thisItem['revision'].date_created + if len(items) >= 1: + timeAgo = datetime.datetime.now(thisItem['revision'].date_created.tzinfo) - thisItem[ + 'revision'].date_created timeDiff = items[-1]['revision'].date_created - thisItem['revision'].date_created timeTogether = False for params in maxTimeDelta: if params['maxAge'] is None or timeAgo <= params['maxAge']: timeTogether = timeDiff < params['group'] break - + sameUser = thisItem['revision'].user == items[-1]['revision'].user thisItem['withPrevious'] = timeTogether & sameUser items.append(thisItem) - context ['object_list'] = items - + context['object_list'] = items - return context \ No newline at end of file + return context From df61225b73f31dce3f1b17ea35a7cf6f1b9e32ea Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 6 Apr 2016 21:57:25 +0100 Subject: [PATCH 009/120] Optimise imports So many unused imports, these have been banished --- RIGS/versioning.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 36786a74..7677fa13 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -1,27 +1,18 @@ import logging -from django.views import generic -from django.core.urlresolvers import reverse_lazy -from django.shortcuts import get_object_or_404 -from django.template import RequestContext -from django.template.loader import get_template -from django.conf import settings -from django.http import HttpResponse -from django.db.models import Q -from django.contrib import messages + from django.core.exceptions import ObjectDoesNotExist +from django.shortcuts import get_object_or_404 +from django.views import generic # Versioning import reversion -import simplejson from reversion.models import Version from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from django.db.models import ForeignKey, IntegerField, EmailField, TextField +from django.db.models import IntegerField, EmailField, TextField from diff_match_patch import diff_match_patch -from RIGS import models, forms +from RIGS import models import datetime -import re logger = logging.getLogger('tec.pyrigs') From b47cfed5a9886793e6ff53e1f918f25a8f1b7836 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 7 Apr 2016 00:19:18 +0100 Subject: [PATCH 010/120] Fixed versioning UI for revisions containing multiple event versions, hopefully --- RIGS/versioning.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 7677fa13..718bcf95 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -119,17 +119,19 @@ def compare_event_items(old, new): # 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 - compare = ItemCompare(old=version.object_version.object) - item_dict[version.object_id] = compare + if version.object_version.object.event.id == old.object_id: + compare = ItemCompare(old=version.object_version.object) + item_dict[version.object_id] = compare for version in new_item_versions: # go through the new versions - 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 = ItemCompare(new=version.object_version.object) + if version.object_version.object.event.id == new.object_id: + 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 = ItemCompare(new=version.object_version.object) - 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 item_dict.items(): From f1bd1ca674b9e554402b6c08bc71157f624b5f1b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 7 Apr 2016 00:52:33 +0100 Subject: [PATCH 011/120] Make 'created' noun class-specific. Closes #185 --- RIGS/templates/RIGS/activity_table.html | 2 +- RIGS/templates/RIGS/version_history.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/activity_table.html b/RIGS/templates/RIGS/activity_table.html index 1d491663..049e9aad 100644 --- a/RIGS/templates/RIGS/activity_table.html +++ b/RIGS/templates/RIGS/activity_table.html @@ -72,7 +72,7 @@ {{ version.revision.user.name }} {% if version.old == None %} - Object Created + {{object|to_class_name}} Created {% else %} {% include 'RIGS/version_changes.html' %} {% endif %} diff --git a/RIGS/templates/RIGS/version_history.html b/RIGS/templates/RIGS/version_history.html index 924ea148..3261c5df 100644 --- a/RIGS/templates/RIGS/version_history.html +++ b/RIGS/templates/RIGS/version_history.html @@ -47,7 +47,7 @@ {{ version.revision.user.name }} {% if version.old == None %} - Object Created + {{object|to_class_name}} Created {% else %} {% include 'RIGS/version_changes.html' %} {% endif %} From 767b5512e312d7b152c3a8a866146d47c07a07ed Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 7 Apr 2016 00:53:57 +0100 Subject: [PATCH 012/120] Fail, missed a change, actually closes #185 now --- RIGS/templates/RIGS/activity_table.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/activity_table.html b/RIGS/templates/RIGS/activity_table.html index 049e9aad..1a1c8c1d 100644 --- a/RIGS/templates/RIGS/activity_table.html +++ b/RIGS/templates/RIGS/activity_table.html @@ -72,7 +72,7 @@ {{ version.revision.user.name }} {% if version.old == None %} - {{object|to_class_name}} Created + {{version.new|to_class_name}} Created {% else %} {% include 'RIGS/version_changes.html' %} {% endif %} From f8a2a7a9593bc63a8922fa9200d096f9b3ca5325 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 7 Apr 2016 09:23:35 +0100 Subject: [PATCH 013/120] Made items display in version history again --- RIGS/versioning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 718bcf95..ee3da1d8 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -119,12 +119,12 @@ def compare_event_items(old, new): # 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.object_version.object.event.id == old.object_id: + if version.object_version.object.event.id == old.object_id_int: compare = ItemCompare(old=version.object_version.object) item_dict[version.object_id] = compare for version in new_item_versions: # go through the new versions - if version.object_version.object.event.id == new.object_id: + if version.object_version.object.event.id == new.object_id_int: 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 From 98f28aaafd0a5756f5346abacbaecc45529ade36 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 12 Apr 2016 20:15:13 +0100 Subject: [PATCH 014/120] Based on has name as well as MIC - Fix #224 --- RIGS/templates/RIGS/event_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index e68e73bf..81658663 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -151,7 +151,7 @@ {% if object.based_on.is_rig %}N{{ object.based_on.pk|stringformat:"05d" }}{% else %} {{ object.based_on.pk }}{% endif %} - {{ object.base_on.name }} {% if object.based_on.mic %}by {{ object.based_on.mic.name }}{% endif %} + {{ object.based_on.name }} {% if object.based_on.mic %}by {{ object.based_on.mic.name }}{% endif %} {% endif %} From 29aa13316df95eaab7ab1af9d85e0a73dcd774b5 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 27 Apr 2016 20:49:42 +0100 Subject: [PATCH 015/120] Fix for versioning when event has been deleted, closes #226 --- RIGS/versioning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index ee3da1d8..6d524fc0 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -119,12 +119,12 @@ def compare_event_items(old, new): # 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.object_version.object.event.id == old.object_id_int: + if version.field_dict["event"] == old.object_id_int: compare = ItemCompare(old=version.object_version.object) item_dict[version.object_id] = compare for version in new_item_versions: # go through the new versions - if version.object_version.object.event.id == new.object_id_int: + if version.field_dict["event"] == new.object_id_int: 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 From ef815360663034e03948dc75ae4c5d43780fd1be Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 19 May 2016 14:21:21 +0100 Subject: [PATCH 016/120] Add tooltips to rig type selectors. Closes #227 --- RIGS/templates/RIGS/event_form.html | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/RIGS/templates/RIGS/event_form.html b/RIGS/templates/RIGS/event_form.html index 0e7aa6ad..0b855f17 100644 --- a/RIGS/templates/RIGS/event_form.html +++ b/RIGS/templates/RIGS/event_form.html @@ -29,9 +29,9 @@ } function setTime02Hours() { - var id_start = "{{ form.start_date.id_for_label }}" - var id_end_date = "{{ form.end_date.id_for_label }}" - var id_end_time = "{{ form.end_time.id_for_label }}" + var id_start = "{{ form.start_date.id_for_label }}"; + var id_end_date = "{{ form.end_date.id_for_label }}"; + var id_end_time = "{{ form.end_time.id_for_label }}"; if ($('#'+id_start).val() == $('#'+id_end_date).val()) { var end_date = new Date($('#'+id_end_date).val()); end_date.setDate(end_date.getDate() + 1); @@ -67,7 +67,7 @@ $('#{{form.is_rig.auto_id}}').prop('checked', false); $('.form-is_rig').slideUp(); } - }) + }); {% endif %} function supportsDate() { @@ -106,7 +106,7 @@ }); } - }) + }); $(document).ready(function () { setupItemTable($("#{{ form.items_json.id_for_label }}").val()); @@ -150,10 +150,12 @@
- + - +
From 2913b254b4fb808a281a9952abfb304c000200d9 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 19 May 2016 14:33:33 +0100 Subject: [PATCH 017/120] Fix an overflow issue with dropdown fields. Closes #204 --- RIGS/templates/RIGS/event_form.html | 1 + 1 file changed, 1 insertion(+) diff --git a/RIGS/templates/RIGS/event_form.html b/RIGS/templates/RIGS/event_form.html index 0b855f17..28e307ca 100644 --- a/RIGS/templates/RIGS/event_form.html +++ b/RIGS/templates/RIGS/event_form.html @@ -63,6 +63,7 @@ } else { $('.form-is_rig').slideDown(); } + $('.form-hws').css('overflow', 'visible'); } else { $('#{{form.is_rig.auto_id}}').prop('checked', false); $('.form-is_rig').slideUp(); From 1faf8f95c8f8b9b6a678e43af4982c5e75209806 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 22 May 2016 23:13:23 +0100 Subject: [PATCH 018/120] Fixed colour coding on invoice waiting page --- RIGS/templates/RIGS/event_invoice.html | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index c16c501b..805eb3d9 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -26,16 +26,18 @@ {% for object in object_list %} N{{ object.pk|stringformat:"05d" }} {{ object.end_date }} From e4a955f3239cf285f8e7c62bef57f21d7fa531fd Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 23 May 2016 12:36:21 +0100 Subject: [PATCH 019/120] Reformat code to PEP8 --- RIGS/finance.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 73552f2e..d719c66f 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -1,20 +1,19 @@ import cStringIO as StringIO +import datetime +import re +from django.contrib import messages from django.core.urlresolvers import reverse_lazy -from django.db import connection from django.http import Http404, HttpResponseRedirect -from django.views import generic -from django.template import RequestContext -from django.template.loader import get_template from django.http import HttpResponse from django.shortcuts import get_object_or_404 -from django.contrib import messages -import datetime +from django.template import RequestContext +from django.template.loader import get_template +from django.views import generic from z3c.rml import rml2pdf from RIGS import models -import re class InvoiceIndex(generic.ListView): model = models.Invoice @@ -40,6 +39,7 @@ class InvoiceIndex(generic.ListView): class InvoiceDetail(generic.DetailView): model = models.Invoice + class InvoicePrint(generic.View): def get(self, request, pk): invoice = get_object_or_404(models.Invoice, pk=pk) @@ -54,8 +54,8 @@ class InvoicePrint(generic.View): 'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF', } }, - 'invoice':invoice, - 'current_user':request.user, + 'invoice': invoice, + 'current_user': request.user, }) rml = template.render(context) @@ -72,6 +72,7 @@ class InvoicePrint(generic.View): response.write(pdfData) return response + class InvoiceVoid(generic.View): def get(self, *args, **kwargs): pk = kwargs.get('pk') @@ -119,7 +120,7 @@ class InvoiceEvent(generic.View): class PaymentCreate(generic.CreateView): model = models.Payment - fields = ['invoice','date','amount','method'] + fields = ['invoice', 'date', 'amount', 'method'] def get_initial(self): initial = super(generic.CreateView, self).get_initial() @@ -139,4 +140,4 @@ class PaymentDelete(generic.DeleteView): model = models.Payment def get_success_url(self): - return self.request.POST.get('next') \ No newline at end of file + return self.request.POST.get('next') From 60302889569679ad9d41be940b2b50953f4b1761 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 24 May 2016 17:17:52 +0100 Subject: [PATCH 020/120] Cheap and dirty active totals --- RIGS/finance.py | 14 ++- RIGS/models.py | 125 +++++++++++++------------- RIGS/templates/RIGS/invoice_list.html | 2 +- 3 files changed, 77 insertions(+), 64 deletions(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index d719c66f..57721a9f 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -19,16 +19,24 @@ class InvoiceIndex(generic.ListView): model = models.Invoice template_name = 'RIGS/invoice_list.html' + def get_context_data(self, **kwargs): + context = super(InvoiceIndex, self).get_context_data(**kwargs) + total = 0 + for i in context['object_list']: + total += i.balance + context['total'] = total + return context + def get_queryset(self): # Manual query is the only way I have found to do this efficiently. Not ideal but needs must sql = "SELECT * FROM " \ "(SELECT " \ - "(SELECT COUNT(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \ + "(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \ "(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \ - "(SELECT SUM(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \ + "(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \ "\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \ "AS sub " \ - "WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \ + "WHERE (cost - payments) <> 0.0 AND void = '0'" \ "ORDER BY invoice_date" query = self.model.objects.raw(sql) diff --git a/RIGS/models.py b/RIGS/models.py index fc06911f..535aa42c 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -1,31 +1,32 @@ +import datetime import hashlib -import datetime, pytz - -from django.db import models, connection -from django.contrib.auth.models import AbstractUser -from django.conf import settings -from django.utils.functional import cached_property -from django.utils.encoding import python_2_unicode_compatible -import reversion -import string +import pytz import random +import string from collections import Counter -from django.core.urlresolvers import reverse_lazy -from django.core.exceptions import ValidationError - from decimal import Decimal +import reversion +from django.conf import settings +from django.contrib.auth.models import AbstractUser +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse_lazy +from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.utils.functional import cached_property + + # Create your models here. @python_2_unicode_compatible class Profile(AbstractUser): initials = models.CharField(max_length=5, unique=True, null=True, blank=False) phone = models.CharField(max_length=13, null=True, blank=True) - api_key = models.CharField(max_length=40,blank=True,editable=False, null=True) + api_key = models.CharField(max_length=40, blank=True, editable=False, null=True) @classmethod def make_api_key(cls): - size=20 - chars=string.ascii_letters + string.digits + size = 20 + chars = string.ascii_letters + string.digits new_api_key = ''.join(random.choice(chars) for x in range(size)) return new_api_key; @@ -55,6 +56,7 @@ class Profile(AbstractUser): ('view_profile', 'Can view Profile'), ) + class RevisionMixin(object): @property def last_edited_at(self): @@ -79,10 +81,11 @@ class RevisionMixin(object): versions = reversion.get_for_object(self) if versions: version = reversion.get_for_object(self)[0] - return "V{0} | R{1}".format(version.pk,version.revision.pk) + return "V{0} | R{1}".format(version.pk, version.revision.pk) else: return None + @reversion.register @python_2_unicode_compatible class Person(models.Model, RevisionMixin): @@ -97,7 +100,7 @@ class Person(models.Model, RevisionMixin): def __str__(self): string = self.name if self.notes is not None: - if len(self.notes) > 0: + if len(self.notes) > 0: string += "*" return string @@ -108,7 +111,7 @@ class Person(models.Model, RevisionMixin): if e.organisation: o.append(e.organisation) - #Count up occurances and put them in descending order + # Count up occurances and put them in descending order c = Counter(o) stats = c.most_common() return stats @@ -141,7 +144,7 @@ class Organisation(models.Model, RevisionMixin): def __str__(self): string = self.name if self.notes is not None: - if len(self.notes) > 0: + if len(self.notes) > 0: string += "*" return string @@ -151,8 +154,8 @@ class Organisation(models.Model, RevisionMixin): for e in Event.objects.filter(organisation=self).select_related('person'): if e.person: p.append(e.person) - - #Count up occurances and put them in descending order + + # Count up occurances and put them in descending order c = Counter(p) stats = c.most_common() return stats @@ -238,12 +241,18 @@ class Venue(models.Model, RevisionMixin): class EventManager(models.Manager): def current_events(self): events = self.filter( - (models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end - (models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after - (models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire - (models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT + (models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q( + status=Event.CANCELLED)) | # Starts after with no end + (models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q( + status=Event.CANCELLED)) | # Ends after + (models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q( + status=Event.CANCELLED)) | # Active dry hire + (models.Q(dry_hire=True, checked_in_by__isnull=True) & ( + models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT models.Q(status=Event.CANCELLED, start_date__gte=datetime.date.today()) # Canceled but not started - ).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic') + ).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', + 'organisation', + 'venue', 'mic') return events def events_in_bounds(self, start, end): @@ -251,15 +260,17 @@ class EventManager(models.Manager): (models.Q(start_date__gte=start.date(), start_date__lte=end.date())) | # Start date in bounds (models.Q(end_date__gte=start.date(), end_date__lte=end.date())) | # End date in bounds (models.Q(access_at__gte=start, access_at__lte=end)) | # Access at in bounds - (models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds + (models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds - (models.Q(start_date__lte=start, end_date__gte=end)) | # Start before, end after - (models.Q(access_at__lte=start, start_date__gte=end)) | # Access before, start after - (models.Q(access_at__lte=start, end_date__gte=end)) | # Access before, end after - (models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start after - (models.Q(meet_at__lte=start, end_date__gte=end)) # Meet before, end after + (models.Q(start_date__lte=start, end_date__gte=end)) | # Start before, end after + (models.Q(access_at__lte=start, start_date__gte=end)) | # Access before, start after + (models.Q(access_at__lte=start, end_date__gte=end)) | # Access before, end after + (models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start after + (models.Q(meet_at__lte=start, end_date__gte=end)) # Meet before, end after - ).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic') + ).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', + 'organisation', + 'venue', 'mic') return events def rig_count(self): @@ -301,7 +312,8 @@ class Event(models.Model, RevisionMixin): status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL) dry_hire = models.BooleanField(default=False) is_rig = models.BooleanField(default=True) - based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True, null=True) + based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True, + null=True) # Timing start_date = models.DateField() @@ -327,6 +339,7 @@ class Event(models.Model, RevisionMixin): """ EX Vat """ + @property def sum_total(self): # Manual querying is required for efficiency whilst maintaining floating point arithmetic @@ -334,14 +347,15 @@ class Event(models.Model, RevisionMixin): # sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id # else: # sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id - #total = self.items.raw(sql)[0] - #if total.sum_total: + # total = self.items.raw(sql)[0] + # if total.sum_total: # return total.sum_total - #total = 0.0 - #for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"): + # total = 0.0 + # for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"): # total += item.sum total = EventItem.objects.filter(event=self).aggregate( - sum_total=models.Sum(models.F('cost')*models.F('quantity'), output_field=models.DecimalField(max_digits=10, decimal_places=2)) + sum_total=models.Sum(models.F('cost') * models.F('quantity'), + output_field=models.DecimalField(max_digits=10, decimal_places=2)) )['sum_total'] if total: return total @@ -358,6 +372,7 @@ class Event(models.Model, RevisionMixin): """ Inc VAT """ + @property def total(self): return self.sum_total + self.vat @@ -382,7 +397,7 @@ class Event(models.Model, RevisionMixin): def earliest_time(self): """Finds the earliest time defined in the event - this function could return either a tzaware datetime, or a naiive date object""" - #Put all the datetimes in a list + # Put all the datetimes in a list datetime_list = [] if self.access_at: @@ -394,22 +409,22 @@ class Event(models.Model, RevisionMixin): # If there is no start time defined, pretend it's midnight startTimeFaked = False if self.has_start_time: - startDateTime = datetime.datetime.combine(self.start_date,self.start_time) + startDateTime = datetime.datetime.combine(self.start_date, self.start_time) else: - startDateTime = datetime.datetime.combine(self.start_date,datetime.time(00,00)) + startDateTime = datetime.datetime.combine(self.start_date, datetime.time(00, 00)) startTimeFaked = True - #timezoneIssues - apply the default timezone to the naiive datetime + # timezoneIssues - apply the default timezone to the naiive datetime tz = pytz.timezone(settings.TIME_ZONE) startDateTime = tz.localize(startDateTime) - datetime_list.append(startDateTime) # then add it to the list + datetime_list.append(startDateTime) # then add it to the list - earliest = min(datetime_list).astimezone(tz) #find the earliest datetime in the list + earliest = min(datetime_list).astimezone(tz) # find the earliest datetime in the list # if we faked it & it's the earliest, better own up - if startTimeFaked and earliest==startDateTime: + if startTimeFaked and earliest == startDateTime: return self.start_date - + return earliest @property @@ -421,7 +436,7 @@ class Event(models.Model, RevisionMixin): endDate = self.start_date if self.has_end_time: - endDateTime = datetime.datetime.combine(endDate,self.end_time) + endDateTime = datetime.datetime.combine(endDate, self.end_time) tz = pytz.timezone(settings.TIME_ZONE) endDateTime = tz.localize(endDateTime) @@ -430,7 +445,6 @@ class Event(models.Model, RevisionMixin): else: return endDate - objects = EventManager() def get_absolute_url(self): @@ -446,7 +460,7 @@ class Event(models.Model, RevisionMixin): startEndSameDay = not self.end_date or self.end_date == self.start_date hasStartAndEnd = self.has_start_time and self.has_end_time if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time: - raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.') + raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.') def save(self, *args, **kwargs): """Call :meth:`full_clean` before saving.""" @@ -503,15 +517,6 @@ class Invoice(models.Model): @property def payment_total(self): - # Manual querying is required for efficiency whilst maintaining floating point arithmetic - #if connection.vendor == 'postgresql': - # sql = "SELECT SUM(amount) AS total FROM \"RIGS_payment\" WHERE invoice_id=%i" % self.id - #else: - # sql = "SELECT id, SUM(amount) AS total FROM RIGS_payment WHERE invoice_id=%i" % self.id - #total = self.payment_set.raw(sql)[0] - #if total.total: - # return total.total - #return 0.0 total = self.payment_set.aggregate(total=models.Sum('amount'))['total'] if total: return total @@ -552,4 +557,4 @@ class Payment(models.Model): method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True) def __str__(self): - return "%s: %d" % (self.get_method_display(), self.amount) \ No newline at end of file + return "%s: %d" % (self.get_method_display(), self.amount) diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index bf9fe31f..018ca910 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -5,7 +5,7 @@ {% block content %}
-

Invoices

+

Invoices (£ {{ total|floatformat:2 }})

{% if is_paginated %}
{% paginator %} From 7ccc8faf20a308cfae943aad60a8b77918b459db Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 24 May 2016 17:32:58 +0100 Subject: [PATCH 021/120] Add total for waiting events --- RIGS/finance.py | 8 ++++++++ RIGS/templates/RIGS/event_invoice.html | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 57721a9f..9025e15b 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -103,6 +103,14 @@ class InvoiceWaiting(generic.ListView): paginate_by = 25 template_name = 'RIGS/event_invoice.html' + def get_context_data(self, **kwargs): + context = super(InvoiceWaiting, self).get_context_data(**kwargs) + total = 0 + for obj in context['object_list']: + total += obj.sum_total + context['total'] = total + return context + def get_queryset(self): # @todo find a way to select items events = self.model.objects.filter(is_rig=True, end_date__lt=datetime.date.today(), diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 805eb3d9..29c56f42 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -5,7 +5,7 @@ {% block content %}
-

Events for Invoice

+

Events for Invoice (£ {{ total|floatformat:2 }})

{% if is_paginated %}
{% paginator %} From 6b77393414682c930b3d091361ec76ea7f3fa0cf Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 24 May 2016 17:49:44 +0100 Subject: [PATCH 022/120] Fix for incorrect selection of active invoices. Make sure waiting shows total across all pages. --- RIGS/finance.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 9025e15b..69eaa513 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -36,7 +36,7 @@ class InvoiceIndex(generic.ListView): "(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \ "\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \ "AS sub " \ - "WHERE (cost - payments) <> 0.0 AND void = '0'" \ + "WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \ "ORDER BY invoice_date" query = self.model.objects.raw(sql) @@ -106,19 +106,24 @@ class InvoiceWaiting(generic.ListView): def get_context_data(self, **kwargs): context = super(InvoiceWaiting, self).get_context_data(**kwargs) total = 0 - for obj in context['object_list']: + for obj in self.get_objects(): total += obj.sum_total context['total'] = total return context def get_queryset(self): + return self.get_objects() + + def get_objects(self): # @todo find a way to select items events = self.model.objects.filter(is_rig=True, end_date__lt=datetime.date.today(), invoice__isnull=True) \ .order_by('start_date') \ .select_related('person', 'organisation', - 'venue', 'mic') + 'venue', 'mic') \ + .prefetch_related('items') + return events From cc2450ff871467a45920a05051268ebe374e2662 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 24 May 2016 17:50:48 +0100 Subject: [PATCH 023/120] Make total conditional if defined --- RIGS/templates/RIGS/invoice_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 018ca910..32abdcec 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -5,7 +5,7 @@ {% block content %}
-

Invoices (£ {{ total|floatformat:2 }})

+

Invoices {% if total %}(£ {{ total|floatformat:2 }}){% endif %}

{% if is_paginated %}
{% paginator %} From db58c113aafe68c131e496d3b8a8cbec490c09e2 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 28 May 2016 14:52:48 +0100 Subject: [PATCH 024/120] Changed font to load over https - #236 --- templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index c27231bd..3cde5acd 100644 --- a/templates/base.html +++ b/templates/base.html @@ -14,7 +14,7 @@ - From 000351d884b441485ab0ff65ca31c8c9bd0a7ee1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 28 May 2016 15:20:15 +0100 Subject: [PATCH 025/120] Redirect all requests to https --- PyRIGS/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index d85ff887..bb9ba676 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -13,6 +13,7 @@ import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +SECURE_SSL_REDIRECT = True # Redirect all http requests to https # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ @@ -55,6 +56,7 @@ INSTALLED_APPS = ( MIDDLEWARE_CLASSES = ( 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', + 'django.middleware.security.SecurityMiddleware', 'reversion.middleware.RevisionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', From aa79f3628e157854764eb26248b026a9c3c0898a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 28 May 2016 15:27:38 +0100 Subject: [PATCH 026/120] Only redirect to HTTPS in production --- PyRIGS/settings.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index bb9ba676..3098c3fb 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -12,9 +12,6 @@ https://docs.djangoproject.com/en/1.7/ref/settings/ import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -SECURE_SSL_REDIRECT = True # Redirect all http requests to https - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ @@ -28,6 +25,10 @@ TEMPLATE_DEBUG = True ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com'] +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +if not DEBUG: + SECURE_SSL_REDIRECT = True # Redirect all http requests to https + INTERNAL_IPS = ['127.0.0.1'] ADMINS = ( From a725ef5caf4fb6e1e3beaa4302a2a12e461d6608 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 29 May 2016 17:09:52 +0100 Subject: [PATCH 027/120] Removed add to google calendar link, closes #237 --- RIGS/templates/RIGS/profile_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index 8fce1e60..e69e7bf0 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -126,7 +126,7 @@
{% if user.api_key %}

-						Click here to add to google calendar.
+ Click here for instructions on adding to google calendar.
To sync from google calendar to mobile device, visit this page on your device and tick "RIGS Calendar".
{% else %}
No API Key Generated
From eaf5c9687edd82269202f64798ebbc664ba88cac Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 29 May 2016 20:21:23 +0100 Subject: [PATCH 028/120] Fixed typo, closes #174 --- PyRIGS/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index a7c1db90..67d8964c 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -58,7 +58,7 @@ def api_key_required(function): try: user_object = models.Profile.objects.get(pk=userid) - except Profile.DoesNotExist: + except models.Profile.DoesNotExist: return error_resp if user_object.api_key != key: From 83302c44399ccf45d1d5431c6ef68520b07ccdc1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 29 May 2016 21:30:05 +0100 Subject: [PATCH 029/120] Invoice UI improvements. Renamed pages, added description, and added total number of events --- RIGS/finance.py | 3 ++- RIGS/templates/RIGS/event_invoice.html | 3 ++- RIGS/templates/RIGS/invoice_list.html | 3 ++- RIGS/templates/RIGS/invoice_list_active.html | 13 +++++++++++++ RIGS/templates/RIGS/invoice_list_archive.html | 13 +++++++++++++ templates/base.html | 2 +- 6 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 RIGS/templates/RIGS/invoice_list_active.html create mode 100644 RIGS/templates/RIGS/invoice_list_archive.html diff --git a/RIGS/finance.py b/RIGS/finance.py index 69eaa513..d604cd57 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -17,7 +17,7 @@ from RIGS import models class InvoiceIndex(generic.ListView): model = models.Invoice - template_name = 'RIGS/invoice_list.html' + template_name = 'RIGS/invoice_list_active.html' def get_context_data(self, **kwargs): context = super(InvoiceIndex, self).get_context_data(**kwargs) @@ -95,6 +95,7 @@ class InvoiceVoid(generic.View): class InvoiceArchive(generic.ListView): model = models.Invoice + template_name = 'RIGS/invoice_list_archive.html' paginate_by = 25 diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 29c56f42..3324911a 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -5,7 +5,8 @@ {% block content %}
-

Events for Invoice (£ {{ total|floatformat:2 }})

+

Events for Invoice ({{object_list|length}} Events, £ {{ total|floatformat:2 }})

+

These events have happened, but paperwork has not yet been sent to treasury

{% if is_paginated %}
{% paginator %} diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 32abdcec..26ddeb12 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -5,7 +5,8 @@ {% block content %}
-

Invoices {% if total %}(£ {{ total|floatformat:2 }}){% endif %}

+

{% block heading %}Invoices{% endblock %}

+ {% block description %}{% endblock %} {% if is_paginated %}
{% paginator %} diff --git a/RIGS/templates/RIGS/invoice_list_active.html b/RIGS/templates/RIGS/invoice_list_active.html new file mode 100644 index 00000000..24cea673 --- /dev/null +++ b/RIGS/templates/RIGS/invoice_list_active.html @@ -0,0 +1,13 @@ +{% extends 'RIGS/invoice_list.html' %} + +{% block title %} +Outstanding Invoices +{% endblock %} + +{% block heading %} +Outstanding Invoices ({{object_list|length}} Events, £ {{ total|floatformat:2 }}) +{% endblock %} + +{% block description %} +

Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger

+{% endblock %} \ No newline at end of file diff --git a/RIGS/templates/RIGS/invoice_list_archive.html b/RIGS/templates/RIGS/invoice_list_archive.html new file mode 100644 index 00000000..77bab204 --- /dev/null +++ b/RIGS/templates/RIGS/invoice_list_archive.html @@ -0,0 +1,13 @@ +{% extends 'RIGS/invoice_list.html' %} + +{% block title %} +Invoice Archive +{% endblock %} + +{% block heading %} +All Invoices +{% endblock %} + +{% block description %} +

This page displays all invoices: outstanding, paid, and void

+{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 3cde5acd..299be06e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -74,7 +74,7 @@
+ {% endif %}
From 3ccbdff737cca2c7390bf511ee990cfed9efbbce Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 29 May 2016 22:51:41 +0100 Subject: [PATCH 035/120] Added balance to invoice page - closes #235 --- RIGS/templates/RIGS/invoice_detail.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index eccec0a3..e5b14a7f 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -111,6 +111,11 @@ {% endfor %} + + Balance: + {{ object.balance|floatformat:2 }} + +
From a48afb91573d9bb59c03c8323786a462f3982d22 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 29 May 2016 22:56:58 +0100 Subject: [PATCH 036/120] Added internal/external indicators to invoice lists --- RIGS/templates/RIGS/event_invoice.html | 5 +++++ RIGS/templates/RIGS/invoice_list.html | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 94d84f04..79098853 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -57,9 +57,14 @@ {% if object.organisation %} {{ object.organisation.name }} +
+ {{ object.organisation.union_account|yesno:'Internal,External' }} {% else %} {{ object.person.name }} +
+ External {% endif %} + {{ object.sum_total|floatformat:2 }} diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index cb63b7f5..f7d23fcf 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -33,9 +33,14 @@ {% if object.organisation %} {{ object.event.organisation.name }} +
+ {{ object.organisation.union_account|yesno:'Internal,External' }} {% else %} {{ object.event.person.name }} - {% endif %} +
+ External + {% endif %} + {{ object.event.start_date }} {{ object.invoice_date }} {{ object.balance|floatformat:2 }} From 5cc69cbb413ac1adbc615f324cfed28f27ea5f31 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 29 May 2016 23:03:41 +0100 Subject: [PATCH 037/120] Stopped things opening in a new window, because it's really annoying. If you want to do this, use the appropriate keyboard shortcut or mouse button --- RIGS/templates/RIGS/event_invoice.html | 4 ++-- RIGS/templates/RIGS/invoice_list.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 79098853..0e56158f 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -50,7 +50,7 @@ danger {% endif %} "> - N{{ object.pk|stringformat:"05d" }}
+ N{{ object.pk|stringformat:"05d" }}
{{ object.get_status_display }} {{ object.start_date }} {{ object.name }} @@ -76,7 +76,7 @@ {% endif %} - + diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index f7d23fcf..f833b89a 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -28,7 +28,7 @@ {% for object in object_list %} {{ object.pk }} - N{{ object.event.pk|stringformat:"05d" }}: {{ object.event.name }}
+ N{{ object.event.pk|stringformat:"05d" }}: {{ object.event.name }}
{{ object.event.get_status_display }} {% if object.organisation %} From 68b35c2d249763f9d5e60a717f9629ebabe6b226 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 29 May 2016 23:47:56 +0100 Subject: [PATCH 038/120] Removed print button conditions following discussion --- RIGS/templates/RIGS/invoice_detail.html | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index e5b14a7f..ad177ba4 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -15,12 +15,10 @@ - {% if not object.event.organisation.union_account %} - -
- {% endif %} + +
From 9108cb3c4e264b38455b99c57b35deb0398912dc Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 4 Jun 2016 18:08:43 +0100 Subject: [PATCH 039/120] Fail! Hide non-rigs from waiting invoices --- RIGS/finance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/RIGS/finance.py b/RIGS/finance.py index be52b1f1..1fd9b535 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -124,6 +124,7 @@ class InvoiceWaiting(generic.ListView): Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end Q(end_date__lte=datetime.date.today()) # Has end date, finishes before ) & Q(invoice__isnull=True) # Has not already been invoiced + & Q(is_rig=True) # Is a rig (not non-rig) ).order_by('start_date') \ .select_related('person', From f92f418bc5cff671930a52bdab319cc8704553c1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 6 Jun 2016 23:06:30 +0100 Subject: [PATCH 040/120] Fixed waiting invoice counter - closes #239 --- RIGS/finance.py | 1 + RIGS/templates/RIGS/event_invoice.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 1fd9b535..75de8bd9 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -112,6 +112,7 @@ class InvoiceWaiting(generic.ListView): for obj in self.get_objects(): total += obj.sum_total context['total'] = total + context['count'] = len(self.get_objects()) return context def get_queryset(self): diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 0e56158f..3136fe40 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -15,7 +15,7 @@ {% block content %}
-

Events for Invoice ({{object_list|length}} Events, £ {{ total|floatformat:2 }})

+

Events for Invoice ({{count}} Events, £ {{ total|floatformat:2 }})

These events have happened, but paperwork has not yet been sent to treasury

{% if is_paginated %}
From 1163b117e4020421b0b4ce33f7abec330c8a5505 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 14 Jun 2016 19:23:40 +0100 Subject: [PATCH 041/120] Fixed #240 --- RIGS/templates/RIGS/invoice_list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index f833b89a..7b080897 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -31,10 +31,10 @@ N{{ object.event.pk|stringformat:"05d" }}: {{ object.event.name }}
{{ object.event.get_status_display }} - {% if object.organisation %} + {% if object.event.organisation %} {{ object.event.organisation.name }}
- {{ object.organisation.union_account|yesno:'Internal,External' }} + {{ object.event.organisation.union_account|yesno:'Internal,External' }} {% else %} {{ object.event.person.name }}
From f265da2f1d9277a404c849d1004fb7a2907c6df0 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 14 Jun 2016 19:45:45 +0100 Subject: [PATCH 042/120] Fixed #241 and #244 --- RIGS/templates/RIGS/event_invoice.html | 124 +++++++++++++------------ RIGS/templates/RIGS/invoice_list.html | 82 ++++++++-------- 2 files changed, 105 insertions(+), 101 deletions(-) diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 3136fe40..05b351aa 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -22,68 +22,70 @@ {% paginator %}
{% endif %} - - - - - - - - - - - - - - {% for object in object_list %} - - - - - - - - +
+
#Start DateEvent NameClientCost
{{ object.start_date }}{{ object.name }} - {% if object.organisation %} - {{ object.organisation.name }} -
- {{ object.organisation.union_account|yesno:'Internal,External' }} - {% else %} - {{ object.person.name }} -
- External - {% endif %} - -
{{ object.sum_total|floatformat:2 }} - {% if object.mic %} - {{ object.mic.initials }}
- - {% else %} - - {% endif %} -
- - - -
+ + + + + + + + + - {% endfor %} - -
#Start DateEvent NameClientCostMIC
+ + + {% for object in object_list %} + + N{{ object.pk|stringformat:"05d" }}
+ {{ object.get_status_display }} + {{ object.start_date }} + {{ object.name }} + + {% if object.organisation %} + {{ object.organisation.name }} +
+ {{ object.organisation.union_account|yesno:'Internal,External' }} + {% else %} + {{ object.person.name }} +
+ External + {% endif %} + + + {{ object.sum_total|floatformat:2 }} + + {% if object.mic %} + {{ object.mic.initials }}
+ + {% else %} + + {% endif %} + + + + + + + + {% endfor %} + + +
{% if is_paginated %}
{% paginator %} diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 7b080897..2c77d352 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -12,47 +12,49 @@ {% paginator %}
{% endif %} - - - - - - - - - - - - - - {% for object in object_list %} - - - - - - - - - +
+
#EventClientEvent DateInvoice DateBalance
{{ object.pk }}N{{ object.event.pk|stringformat:"05d" }}: {{ object.event.name }}
- {{ object.event.get_status_display }}
{% if object.event.organisation %} - {{ object.event.organisation.name }} -
- {{ object.event.organisation.union_account|yesno:'Internal,External' }} - {% else %} - {{ object.event.person.name }} -
- External - {% endif %} -
{{ object.event.start_date }}{{ object.invoice_date }}{{ object.balance|floatformat:2 }} - - - -
+ + + + + + + + + - {% endfor %} - -
#EventClientEvent DateInvoice DateBalance
+ + + {% for object in object_list %} + + {{ object.pk }} + N{{ object.event.pk|stringformat:"05d" }}: {{ object.event.name }}
+ {{ object.event.get_status_display }} + + {% if object.event.organisation %} + {{ object.event.organisation.name }} +
+ {{ object.event.organisation.union_account|yesno:'Internal,External' }} + {% else %} + {{ object.event.person.name }} +
+ External + {% endif %} + + {{ object.event.start_date }} + {{ object.invoice_date }} + {{ object.balance|floatformat:2 }} + + + + + + + {% endfor %} + + +
{% if is_paginated %}
{% paginator %} From a7247c273edaf26e73a520cc753807f671f8177f Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 14 Jun 2016 19:50:35 +0100 Subject: [PATCH 043/120] Fixed #245 --- RIGS/templates/RIGS/event_table.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/event_table.html b/RIGS/templates/RIGS/event_table.html index 1868f2dd..949c710a 100644 --- a/RIGS/templates/RIGS/event_table.html +++ b/RIGS/templates/RIGS/event_table.html @@ -1,7 +1,7 @@
- + @@ -23,7 +23,7 @@ danger {% endif %} "> - +
# Event Date Event Details Event Timings{{ event.pk }}
{{ event.start_date|date:"D d/m/Y" }}
{% if event.end_date and event.end_date != event.start_date %} From e1578eb0d47b8d5a17a57b23b828e32b61a265cc Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 15 Jun 2016 13:00:14 +0100 Subject: [PATCH 044/120] Fixed responsive table fail --- RIGS/templates/RIGS/event_invoice.html | 2 +- RIGS/templates/RIGS/invoice_list.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 05b351aa..cd080a74 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -22,7 +22,7 @@ {% paginator %} {% endif %} -
+
diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 2c77d352..5bd7ef70 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -12,7 +12,7 @@ {% paginator %} {% endif %} -
+
From 67624eea6f2bae4f44f5c7ae179182d67aed1949 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 15 Jun 2016 23:18:46 +0100 Subject: [PATCH 045/120] Allow deleting invoices, if there are no payments yet --- RIGS/finance.py | 19 ++++++++++ .../RIGS/invoice_confirm_delete.html | 35 +++++++++++++++++++ RIGS/templates/RIGS/invoice_detail.html | 4 +++ RIGS/urls.py | 3 ++ 4 files changed, 61 insertions(+) create mode 100644 RIGS/templates/RIGS/invoice_confirm_delete.html diff --git a/RIGS/finance.py b/RIGS/finance.py index 75de8bd9..65fc3b07 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -94,6 +94,25 @@ class InvoiceVoid(generic.View): return HttpResponseRedirect(reverse_lazy('invoice_list')) return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk})) +class InvoiceDelete(generic.DeleteView): + model = models.Invoice + + def get(self, request, pk): + obj = self.get_object() + if obj.payment_set.all().count() > 0: + messages.info(self.request, 'To delete an invoice, delete the payments first.') + return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk})) + return super(InvoiceDelete, self).get(pk) + + def post(self, request, pk): + obj = self.get_object() + if obj.payment_set.all().count() > 0: + messages.info(self.request, 'To delete an invoice, delete the payments first.') + return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk})) + return super(InvoiceDelete, self).post(pk) + + def get_success_url(self): + return self.request.POST.get('next') class InvoiceArchive(generic.ListView): model = models.Invoice diff --git a/RIGS/templates/RIGS/invoice_confirm_delete.html b/RIGS/templates/RIGS/invoice_confirm_delete.html new file mode 100644 index 00000000..fe295ed6 --- /dev/null +++ b/RIGS/templates/RIGS/invoice_confirm_delete.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %} + +{% block content %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index ad177ba4..27b4c514 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -11,6 +11,10 @@
+ + + diff --git a/RIGS/urls.py b/RIGS/urls.py index 4338df08..ee949ff8 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -127,6 +127,9 @@ urlpatterns = patterns('', url(r'^invoice/(?P\d+)/void/$', permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()), name='invoice_void'), + url(r'^invoice/(?P\d+)/delete/$', + permission_required_with_403('RIGS.change_invoice')(finance.InvoiceDelete.as_view()), + name='invoice_delete'), url(r'^payment/create/$', permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()), name='payment_create'), From 667b0c80ca7bc7e8fd2eae8839f969d61c46dcd1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 16 Jun 2016 01:44:59 +0100 Subject: [PATCH 046/120] Added tests for invoice deleting --- RIGS/test_unit.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 75fcca5a..c51874f4 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -155,3 +155,57 @@ class TestAdminMergeObjects(TestCase): if event.organisation == self.organisations[3]: # The one we left in place continue self.assertEqual(updatedEvent.organisation, self.organisations[1]) + +class TestInvoiceDelete(TestCase): + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + + cls.events = { + 1: models.Event.objects.create(name="TE E1", start_date=date.today()), + 2: models.Event.objects.create(name="TE E2", start_date=date.today()) + } + + cls.invoices = { + 1: models.Invoice.objects.create(event=cls.events[1]), + 2: models.Invoice.objects.create(event=cls.events[2]) + } + + cls.payments = { + 1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34, method=models.Payment.CASH) + } + + def setUp(self): + self.profile.set_password('testuser') + self.profile.save() + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + + def test_invoice_delete_allowed(self): + request_url = reverse('invoice_delete', kwargs={'pk':self.invoices[2].pk}) + + response = self.client.get(request_url, follow=True) + self.assertContains(response, "Are you sure") + + # Check the invoice still exists + self.assertTrue(models.Invoice.objects.get(pk=self.invoices[2].pk)) + + # Actually delete it + response = self.client.post(request_url, follow=True) + + # Check the invoice is deleted + self.assertRaises(ObjectDoesNotExist, models.Invoice.objects.get, pk=self.invoices[2].pk) + + def test_invoice_delete_not_allowed(self): + request_url = reverse('invoice_delete', kwargs={'pk':self.invoices[1].pk}) + + response = self.client.get(request_url, follow=True) + self.assertContains(response, "To delete an invoice, delete the payments first.") + + # Check the invoice still exists + self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) + + # Try to actually delete it + response = self.client.post(request_url, follow=True) + + # Check this didn't work + self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) \ No newline at end of file From 05b2de561e3c0a18c05e7f2fcbf0d9fa19bde33a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 10 Jul 2016 09:54:35 +0100 Subject: [PATCH 047/120] Added phone links - closes #247 --- RIGS/templates/RIGS/organisation_list.html | 2 +- RIGS/templates/RIGS/person_list.html | 2 +- RIGS/templates/RIGS/profile_detail.html | 2 +- RIGS/templates/RIGS/venue_list.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RIGS/templates/RIGS/organisation_list.html b/RIGS/templates/RIGS/organisation_list.html index 2c88408a..c8856886 100644 --- a/RIGS/templates/RIGS/organisation_list.html +++ b/RIGS/templates/RIGS/organisation_list.html @@ -45,7 +45,7 @@
- + - + - + - +
{{ object.pk }} {{ object.name }} {{ object.email }}{{ object.phone }}{{ object.phone }} {{ object.notes|yesno|capfirst }} {{ object.union_account|yesno|capfirst }} diff --git a/RIGS/templates/RIGS/person_list.html b/RIGS/templates/RIGS/person_list.html index a7c1c08d..2cbdff8e 100644 --- a/RIGS/templates/RIGS/person_list.html +++ b/RIGS/templates/RIGS/person_list.html @@ -44,7 +44,7 @@ {{ person.pk }} {{ person.name }} {{ person.email }}{{ person.phone }}{{ person.phone }} {{ person.notes|yesno|capfirst }} diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index e69e7bf0..11904b18 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -71,7 +71,7 @@
{{object.initials}}
Phone
-
{{object.phone}}
+
{{object.phone}}
{% if not request.is_ajax %} {% if object.pk == user.pk %} diff --git a/RIGS/templates/RIGS/venue_list.html b/RIGS/templates/RIGS/venue_list.html index cf89686c..88ae61ac 100644 --- a/RIGS/templates/RIGS/venue_list.html +++ b/RIGS/templates/RIGS/venue_list.html @@ -45,7 +45,7 @@
{{ object.pk }} {{ object.name }} {{ object.email }}{{ object.phone }}{{ object.phone }} {{ object.notes|yesno|capfirst }} From 39d27d273050cec057b60ebef4d049713f971f06 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 10 Jul 2016 10:49:23 +0100 Subject: [PATCH 048/120] Basic invoice UI improvements - closes #232 --- RIGS/finance.py | 1 + RIGS/models.py | 4 +++ RIGS/templates/RIGS/event_detail.html | 42 +++++++++++++++++++------ RIGS/templates/RIGS/invoice_detail.html | 2 +- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 75de8bd9..5cc5c0c1 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -144,6 +144,7 @@ class InvoiceEvent(generic.View): if created: invoice.invoice_date = datetime.date.today() + messages.success(self.request, 'Invoice created successfully') return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk})) diff --git a/RIGS/models.py b/RIGS/models.py index 535aa42c..0650d81c 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -526,6 +526,10 @@ class Invoice(models.Model): def balance(self): return self.sum_total - self.payment_total + @property + def is_closed(self): + return self.balance == 0 or self.void + def __str__(self): return "%i: %s (%.2f)" % (self.pk, self.event, self.balance) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 81658663..d4b089a9 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -25,9 +25,17 @@ class="hidden-xs">Duplicate {% if event.is_rig %} {% if perms.RIGS.add_invoice %} - + + {% endif %} {% endif %} @@ -190,9 +198,17 @@ class="hidden-xs">Duplicate {% if event.is_rig %} {% if perms.RIGS.add_invoice %} - + + {% endif %} {% endif %} @@ -227,9 +243,17 @@ class="hidden-xs">Duplicate {% if event.is_rig %} {% if perms.RIGS.add_invoice %} - + + {% endif %} {% endif %} diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index ad177ba4..2973c358 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -38,7 +38,7 @@
-
+
Event Details
From 01d2eae7bcffd4c36684be2a15afec8da78fabd3 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 10 Jul 2016 11:14:24 +0100 Subject: [PATCH 049/120] More invoice UI improvements - makes colouring consistent - also closes #242 --- RIGS/templates/RIGS/invoice_detail.html | 7 +++++-- RIGS/templates/RIGS/invoice_list.html | 19 +++++++++++++++++-- templates/base.html | 4 ++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index 2973c358..af26ad21 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -38,8 +38,11 @@
-
-
Event Details
+
+
Event Details + {% if object.void %}(VOID){% elif object.is_closed %}(PAID){% else %}(OUTSTANDING){% endif %} + +
Event Number
diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 5bd7ef70..7dc6d8ee 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -29,8 +29,23 @@ {% for object in object_list %}
{{ object.pk }}N{{ object.event.pk|stringformat:"05d" }}: {{ object.event.name }}
- {{ object.event.get_status_display }}
N{{ object.event.pk|stringformat:"05d" }}: {{ object.event.name }}
+ {{ object.event.get_status_display }} + {% if not object.event.mic %}(No MIC){% endif %} +
{% if object.event.organisation %} {{ object.event.organisation.name }} diff --git a/templates/base.html b/templates/base.html index 7a2d15f9..24cc30bc 100644 --- a/templates/base.html +++ b/templates/base.html @@ -76,9 +76,9 @@
From 5949ff74ecce0c92289a062e772f6b8f1a2d5be0 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 22:55:27 +0100 Subject: [PATCH 103/120] Added javascript cookie check, if blocked, login in new tab --- RIGS/rigboard.py | 2 +- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 11 +++++++++-- templates/base_embed.html | 2 +- templates/registration/login_embed.html | 25 ++++++++++++++++++++++--- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 618f6bea..fc14a89e 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -58,7 +58,7 @@ class EventOembed(generic.View): full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { - 'html': ''.format(full_url), + 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', } diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 523fd217..04123b55 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;background:#e9e9e9;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;background:none;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%;background:#fff}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index 464b346e..e31bb58e 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -150,10 +150,16 @@ ins { html.embedded{ min-height:100%; + display: table; + background: #e9e9e9; + width: 100%; body{ - min-height:100%; padding:0; + display: table-cell; + vertical-align: middle; + background: none; + width:100%; } .embed_container{ @@ -161,8 +167,9 @@ html.embedded{ padding:12px 0px; min-height:100%; width:100%; + background: #fff; } - + .source{ background: url('/static/imgs/pyrigs-avatar.png') no-repeat; background-size: 16px 16px; diff --git a/templates/base_embed.html b/templates/base_embed.html index fe987b90..24259ee5 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -26,7 +26,7 @@ {% include "analytics.html" %} -
+
{% block content %} {% endblock %} diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index 32fbb8dd..7f50a7bc 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -3,6 +3,18 @@ {% block title %}Login{% endblock %} +{% block js %} + +{% endblock %} + {% block content %}

Rig Information Gathering System

@@ -10,9 +22,11 @@ {% include 'form_errors.html' %} + +
-
{% csrf_token %} + {% csrf_token %}
{% render_field form.username class+="form-control" placeholder=form.username.label %} @@ -21,9 +35,14 @@ {% render_field form.password class+="form-control" placeholder=form.password.label %}
- - +
+ + +
+
From 2d5f76852334be318242477d69ff5ea9d519abaf Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 10:32:58 +0100 Subject: [PATCH 104/120] Added cookie check with nice error message --- RIGS/views.py | 22 ++++++++++++++++++++-- templates/base_embed.html | 10 ++++++++++ templates/registration/login_embed.html | 15 --------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/RIGS/views.py b/RIGS/views.py index fded9865..c0186bed 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -12,6 +12,8 @@ from django.contrib import messages import datetime, pytz import operator from registration.views import RegistrationView +from django.views.decorators.csrf import csrf_exempt + from RIGS import models, forms @@ -29,21 +31,37 @@ class Index(generic.TemplateView): def login(request, **kwargs): if request.user.is_authenticated(): next = request.REQUEST.get('next', '/') - return HttpResponseRedirect(request.REQUEST.get('next', '/')) + return HttpResponseRedirect(next) else: from django.contrib.auth.views import login return login(request) + +# This view should be exempt from requiring CSRF token. +# Then we can check for it and show a nice error +# Don't worry, django.contrib.auth.views.login will +# check for it before logging the user in +@csrf_exempt def login_embed(request, **kwargs): + print("Running LOGIN") if request.user.is_authenticated(): next = request.REQUEST.get('next', '/') - return HttpResponseRedirect(request.REQUEST.get('next', '/')) + return HttpResponseRedirect(next) else: from django.contrib.auth.views import login + if request.method == "POST": + csrf_cookie = request.COOKIES.get('csrftoken', None) + + if csrf_cookie is None: + messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.') + request.method = 'GET' # Render the page without trying to login + return login(request, template_name="registration/login_embed.html") + + """ Called from a modal window (e.g. when an item is submitted to an event/invoice). May optionally also include some javascript in a success message to cause a load of diff --git a/templates/base_embed.html b/templates/base_embed.html index 24259ee5..bc7daa1a 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -28,6 +28,16 @@
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% block content %} {% endblock %}
diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index 7f50a7bc..64ef8437 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -3,18 +3,6 @@ {% block title %}Login{% endblock %} -{% block js %} - -{% endblock %} - {% block content %}

Rig Information Gathering System

@@ -36,9 +24,6 @@ {% render_field form.password class+="form-control" placeholder=form.password.label %}
-
From 3fc04616b3c0f36977e9375b8d8fbbd1bc981bb7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 10:36:30 +0100 Subject: [PATCH 105/120] Added test for cookie warning --- RIGS/test_unit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 47d01bae..82a7acca 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -252,6 +252,11 @@ class TestEmbeddedViews(TestCase): response = self.client.get(request_url, follow=True) self.assertEqual(len(response.redirect_chain), 0) + def testLoginCookieWarning(self): + login_url = reverse('login_embed') + response = self.client.post(login_url, follow=True) + self.assertContains(response, "Cookies do not seem to be enabled") + def testXFrameHeaders(self): event_url = reverse('event_embed', kwargs={'pk': 1}) login_url = reverse('login_embed') From 88954eca5c4bbc884c4c2bf95c505b0d721a1718 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 10:40:18 +0100 Subject: [PATCH 106/120] Removed weird background from embed --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 04123b55..682c3e08 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;background:#e9e9e9;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;background:none;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%;background:#fff}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index e31bb58e..be753970 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -151,14 +151,12 @@ ins { html.embedded{ min-height:100%; display: table; - background: #e9e9e9; width: 100%; body{ padding:0; display: table-cell; vertical-align: middle; - background: none; width:100%; } @@ -167,7 +165,6 @@ html.embedded{ padding:12px 0px; min-height:100%; width:100%; - background: #fff; } .source{ From f61158b9c07c0d32bd0faad390a94f2c40555934 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:20:43 +0100 Subject: [PATCH 107/120] Rounded corners, transparent background --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 1 + RIGS/templates/RIGS/event_embed.html | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 682c3e08..3e1d4545 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%;background:none}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index be753970..b0a677ac 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -158,6 +158,7 @@ html.embedded{ display: table-cell; vertical-align: middle; width:100%; + background:none; } .embed_container{ diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index bef2e096..e908e9fd 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -11,7 +11,7 @@
{% if object.mic %} -
+
{% elif object.is_rig %} From 68a46af1a86cc34d54f802542d46f39269216bce Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:22:34 +0100 Subject: [PATCH 108/120] Fixed rounded corner fail --- RIGS/templates/RIGS/event_embed.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index e908e9fd..88d7a95d 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -11,8 +11,8 @@
{% if object.mic %} -
- +
+
{% elif object.is_rig %} From e0cb2f4925b8554fd39f2617f616df290edc2704 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:26:32 +0100 Subject: [PATCH 109/120] Linked RIGS title --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 1 + RIGS/templates/RIGS/event_embed.html | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 3e1d4545..41bd5f39 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%;background:none}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%;background:none}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px;color:#000}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index b0a677ac..074b23cc 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -172,6 +172,7 @@ html.embedded{ background: url('/static/imgs/pyrigs-avatar.png') no-repeat; background-size: 16px 16px; padding-left: 20px; + color: #000; } h3{ diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index 88d7a95d..c689fee7 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -5,7 +5,9 @@
- Rig Information Gathering System + + Rig Information Gathering System +
From 0541a70cecda45ceb312e0610c340d1a0b9fddb4 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:30:13 +0100 Subject: [PATCH 110/120] Fixed event title link (_blank) --- RIGS/templates/RIGS/event_embed.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index c689fee7..f7c13b93 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -22,7 +22,7 @@

- + {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{ object.name }} {% if object.venue %} From 92c77c07e0b084e65528b77d8b5a0b748d23a9b3 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Oct 2016 18:47:13 +0100 Subject: [PATCH 111/120] Fix tailing line breaks --- RIGS/static/scss/screen.scss | 2 +- RIGS/templates/RIGS/event_embed.html | 2 +- templates/base_embed.html | 2 +- templates/registration/login_embed.html | 2 +- templates/registration/loginform.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index 074b23cc..953027a9 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -188,4 +188,4 @@ html.embedded{ .event-mic-photo{ max-width: 3em; } -} \ No newline at end of file +} diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index f7c13b93..a6e3e586 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -103,4 +103,4 @@

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/base_embed.html b/templates/base_embed.html index bc7daa1a..6a7a8741 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -46,4 +46,4 @@ {% block js %} {% endblock %} - \ No newline at end of file + diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index 64ef8437..bddb7333 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -31,4 +31,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/registration/loginform.html b/templates/registration/loginform.html index 94343065..ad7e33d3 100644 --- a/templates/registration/loginform.html +++ b/templates/registration/loginform.html @@ -19,4 +19,4 @@
-
\ No newline at end of file +
From 97decf8c52fab5be92e963e41a96cb9edd84af5c Mon Sep 17 00:00:00 2001 From: Sam Osborne Date: Wed, 19 Oct 2016 00:54:42 +0100 Subject: [PATCH 112/120] Add link to pre-event RA #accessible --- RIGS/templates/RIGS/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index d67f82c9..ad224883 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -25,6 +25,7 @@ TEC Forum TEC Wiki + Pre-Event Risk Assessment Price List Subhire Insurance Form From 6fc89727f2ba1b89c67df3cde3fc05a8332a14ba Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Fri, 21 Oct 2016 15:45:29 +0100 Subject: [PATCH 113/120] Stop PO number from duplicating when copying event Would close #256 --- RIGS/rigboard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index fc14a89e..2e2869ac 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -54,7 +54,7 @@ class EventOembed(generic.View): def get(self, request, pk=None): - embed_url = reverse('event_embed', args=[pk]) + embed_url = reverse('event_embed', args=[pk]) full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { @@ -121,6 +121,7 @@ class EventDuplicate(EventUpdate): old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) new = copy.copy(old) # Make a copy of the object in memory new.based_on = old # Make the new event based on the old event + new.purchase_order = None if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) new.pk = None # This means a new event will be created on save, and all items will be re-created @@ -216,4 +217,4 @@ class EventArchive(generic.ArchiveIndexView): if len(qs) == 0: messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.") - return qs \ No newline at end of file + return qs From 3ce191aaf20d0a3f0abcc6ab81bcd1eb58ab0941 Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Sun, 23 Oct 2016 12:26:35 +0100 Subject: [PATCH 114/120] Update README.md Small adjustment to make GitHub markdown display the table. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 51bfec16..787502ef 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Then load the sample data using the command: python manage.py generateSampleData ``` 4 user accounts are created for convenience: + |Username |Password | |---------|---------| |superuser|superuser| From 90c8b199159375a76580844e6eff8fbd868e9068 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 23 Oct 2016 12:45:27 +0100 Subject: [PATCH 115/120] Added tests for PO non-duplication --- RIGS/test_functional.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 7e5010f7..673dadd1 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -438,7 +438,7 @@ class EventTest(LiveServerTestCase): pass def testEventDuplicate(self): - testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end") + testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end", purchase_order="TESTPO") item1 = models.EventItem( event=testEvent, @@ -498,6 +498,8 @@ class EventTest(LiveServerTestCase): infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') self.assertIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO hasn't carried through + self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) @@ -506,6 +508,8 @@ class EventTest(LiveServerTestCase): #Check that based-on hasn't crept into the old event infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') self.assertNotIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO remains on the old event + self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) # Check the items are as they were table = self.browser.find_element_by_id('item-table') # ID number is known, see above From b939bc5a64a20dc2266586548a47a5b33d111891 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 23 Oct 2016 14:05:25 +0100 Subject: [PATCH 116/120] Do not display "not saved" message when the event has been saved --- RIGS/rigboard.py | 4 ++-- RIGS/test_functional.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 2e2869ac..f85c72fe 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -125,8 +125,8 @@ class EventDuplicate(EventUpdate): if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) new.pk = None # This means a new event will be created on save, and all items will be re-created - - messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.') + else: + messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.') return new diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 673dadd1..f6c60865 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -471,6 +471,9 @@ class EventTest(LiveServerTestCase): self.assertIn("Test Item 1", table.text) self.assertIn("Test Item 2", table.text) + # Check the info message is visible + self.assertIn("Event data duplicated but not yet saved",self.browser.find_element_by_id('content').text) + # Add item form.find_element_by_xpath('//button[contains(@class, "item-add")]').click() wait.until(animation_is_finished()) @@ -489,6 +492,7 @@ class EventTest(LiveServerTestCase): save.click() self.assertNotIn("N0000%d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) + self.assertNotIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Check info message not visible # Check the new items are visible table = self.browser.find_element_by_id('item-table') # ID number is known, see above From caa55fe89a512591a055d484d459c9d892bb97ff Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 4 Nov 2016 13:31:06 +0000 Subject: [PATCH 117/120] Restore pagination to invoice waiting (requested by Emma) --- RIGS/finance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 90de18cd..1e0c91a7 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -122,7 +122,7 @@ class InvoiceArchive(generic.ListView): class InvoiceWaiting(generic.ListView): model = models.Event - # paginate_by = 25 + paginate_by = 25 template_name = 'RIGS/event_invoice.html' def get_context_data(self, **kwargs): From 82aa2785ea5b28926748030b9323e9f42343d5a1 Mon Sep 17 00:00:00 2001 From: Sam Osborne Date: Mon, 6 Feb 2017 15:35:29 +0000 Subject: [PATCH 118/120] Make forum go to actual forum Currently goes to old forum :( --- RIGS/templates/RIGS/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index ad224883..99355dc5 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -23,7 +23,7 @@
- TEC Forum + TEC Forum TEC Wiki Pre-Event Risk Assessment Price List From 0ae7bcaf7cafb08c157533060800143e24cebbad Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 10 Mar 2017 15:26:00 +0000 Subject: [PATCH 119/120] Explicitly define height in oembed JSON --- RIGS/rigboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index f85c72fe..81cf564e 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -61,6 +61,7 @@ class EventOembed(generic.View): 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', + 'height': '250' } json = simplejson.JSONEncoderForHTML().encode(data) From 9b7c84cf0890788a08a3dec71e00cbe78748b1fb Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 28 Mar 2017 16:43:15 +0100 Subject: [PATCH 120/120] Change invoice filename format Change the invoice filename format to include the event number. Closes #279 --- RIGS/finance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 1e0c91a7..26ca0df8 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -78,7 +78,7 @@ class InvoicePrint(generic.View): escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name) response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, escapedEventName) + response['Content-Disposition'] = "filename=Invoice %05d - N%05d | %s.pdf" % (invoice.pk, invoice.event.pk, escapedEventName) response.write(pdfData) return response