From 3c21355375a24606315b71171b400487da3431b4 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 29 Jun 2015 17:38:47 +0100 Subject: [PATCH 01/10] Rewrote web calendar UI to have bootstrap interface. Also has go-to-date function - issue #109 --- RIGS/templates/RIGS/calendar.html | 130 +++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 19 deletions(-) diff --git a/RIGS/templates/RIGS/calendar.html b/RIGS/templates/RIGS/calendar.html index b28a0f67..1572739c 100644 --- a/RIGS/templates/RIGS/calendar.html +++ b/RIGS/templates/RIGS/calendar.html @@ -37,17 +37,8 @@ // options apply to basicDay and agendaDay views } }, - buttonText:{ - today: 'Today', - month: 'Month', - week: 'Week', - day: 'Day' - }, - header:{ - left: 'title', - center: '', - right: 'today prev,next month,agendaWeek,agendaDay' - }, + header:false, + events: function(start_moment, end_moment, timezone, callback) { $.ajax({ @@ -62,10 +53,11 @@ var colours = $(doc).each(function() { thisEvent = []; - colours = {'Provisional': 'orange', - 'Confirmed': 'green' , - 'Booked': 'green' , - 'Cancelled': 'grey' + colours = {'Provisional': '#f0ad4e', + 'Confirmed': '#5cb85c' , + 'Booked': '#5cb85c' , + 'Cancelled': 'grey' , + 'non-rig': '#5bc0de' }; thisEvent['start'] = $(this).attr('start_date'); @@ -84,7 +76,11 @@ thisEvent['title'] = $(this).attr('title'); - thisEvent['color'] = colours[$(this).attr('status')]; + if($(this).attr('is_rig')==true || $(this).attr('status') == "Cancelled"){ + thisEvent['color'] = colours[$(this).attr('status')]; + }else{ + thisEvent['color'] = colours['non-rig']; + } thisEvent['url'] = $(this).attr('url'); @@ -93,19 +89,115 @@ callback(events); } }); + }, + + viewRender: function(view, element){ + // Set the title of the view + $('#calendar-header').text(view.title); + + // Enable/Disable "Today" button as required + if(moment().isBetween(view.intervalStart, view.intervalEnd)){ + //Today is within the current view + $('#today-button').prop('disabled', true); + }else{ + $('#today-button').prop('disabled', false); + } + + // Set active view select button + switch(view.name){ + case 'month': + $('#month-button').addClass('active'); + $('#week-button').removeClass('active'); + $('#day-button').removeClass('active'); + break; + + case 'agendaWeek': + $('#month-button').removeClass('active'); + $('#week-button').addClass('active'); + $('#day-button').removeClass('active'); + break; + + case 'agendaDay': + $('#month-button').removeClass('active'); + $('#week-button').removeClass('active'); + $('#day-button').addClass('active'); + break; + } + } }); - + + // set some button listeners + + $('#next-button').click(function(){ $('#calendar').fullCalendar('next') }); + $('#prev-button').click(function(){ $('#calendar').fullCalendar('prev') }); + $('#today-button').click(function(){ $('#calendar').fullCalendar('today') }); + + $('#month-button').click(function(){ $('#calendar').fullCalendar('changeView','month') }); + $('#week-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaWeek') }); + $('#day-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaDay') }); + + $('#go-to-date-input').change(function(){ + if( moment($('#go-to-date-input').val()).isValid() ){ + $('#go-to-date-button').prop('disabled', false); + }else{ + $('#go-to-date-button').prop('disabled', true); + } + + }); + + $('#go-to-date-button').click(function(){ + day = moment($('#go-to-date-input').val()) ; + if(day.isValid()){ + $('#calendar').fullCalendar( 'gotoDate', day); + }else{ + alert('Invalid Date'); + } + }); + }); {% endblock %} {% block content %} +
-
-
+ +
+
+ +
+ +
+
+ + + + +
+ +
+ +
+ +
+ + +
+ +
+ + + +
+
+
+
+
+
+
{% endblock %} \ No newline at end of file From 6b40123f639925d5be24f077dc3c5c5af0e114fe Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 29 Jun 2015 19:39:24 +0100 Subject: [PATCH 02/10] Added earliest_time property to event --- RIGS/models.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/RIGS/models.py b/RIGS/models.py index 6c4c9c9c..1941069f 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -1,5 +1,5 @@ import hashlib -import datetime +import datetime, pytz from django.db import models, connection from django.contrib.auth.models import AbstractUser @@ -340,6 +340,40 @@ class Event(models.Model, RevisionMixin): def has_end_time(self): return self.end_time is not None + @property + 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 + datetime_list = [] + + if self.access_at: + datetime_list.append(self.access_at) + + if self.meet_at: + datetime_list.append(self.meet_at) + + # 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) + else: + startDateTime = datetime.datetime.combine(self.start_date,datetime.time(00,00)) + startTimeFaked = True + + #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 + + earliest = min(datetime_list) #find the earliest datetime in the list + + # if we faked it & it's the earliest, better own up + if startTimeFaked and earliest==startDateTime: + return self.start_date + + return earliest + objects = EventManager() def get_absolute_url(self): From 68e624f460c490487d0560befb5974dfb916137c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 29 Jun 2015 20:25:46 +0100 Subject: [PATCH 03/10] Streamlined web-calendar logic, also fixed issue #94 --- RIGS/models.py | 19 +++++++++++++ RIGS/templates/RIGS/calendar.html | 45 ++++++++++++------------------- RIGS/views.py | 41 ++++++++++------------------ 3 files changed, 50 insertions(+), 55 deletions(-) diff --git a/RIGS/models.py b/RIGS/models.py index 36612997..9515b208 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -383,6 +383,25 @@ class Event(models.Model, RevisionMixin): return earliest + @property + def latest_time(self): + """Returns the end of the event - this function could return either a tzaware datetime, or a naiive date object""" + + endDate = self.end_date + if endDate is None: + endDate = self.start_date + + if self.has_end_time: + endDateTime = datetime.datetime.combine(endDate,self.end_time) + tz = pytz.timezone(settings.TIME_ZONE) + endDateTime = tz.localize(endDateTime) + + return endDateTime + + else: + return endDate + + objects = EventManager() def get_absolute_url(self): diff --git a/RIGS/templates/RIGS/calendar.html b/RIGS/templates/RIGS/calendar.html index 1572739c..da40a13a 100644 --- a/RIGS/templates/RIGS/calendar.html +++ b/RIGS/templates/RIGS/calendar.html @@ -45,44 +45,33 @@ url: '/api/event', dataType: 'json', data: { - start: moment(start_moment).format("YYYY-MM-DD[T]HH:mm:ss[Z]"), - end: moment(end_moment).format("YYYY-MM-DD[T]HH:mm:ss[Z]") + start: moment(start_moment).format("YYYY-MM-DD[T]HH:mm:ss"), + end: moment(end_moment).format("YYYY-MM-DD[T]HH:mm:ss") }, success: function(doc) { var events = []; - var colours = + colours = {'Provisional': '#f0ad4e', + 'Confirmed': '#5cb85c' , + 'Booked': '#5cb85c' , + 'Cancelled': 'grey' , + 'non-rig': '#5bc0de' + }; $(doc).each(function() { - thisEvent = []; - colours = {'Provisional': '#f0ad4e', - 'Confirmed': '#5cb85c' , - 'Booked': '#5cb85c' , - 'Cancelled': 'grey' , - 'non-rig': '#5bc0de' - }; - - thisEvent['start'] = $(this).attr('start_date'); - if ($(this).attr('start_time')) { - thisEvent['start'] += 'T' + $(this).attr('start_time'); - - } - - if ($(this).attr('end_date')) { - thisEvent['end'] = $(this).attr('end_date'); - if ($(this).attr('end_time')) { - thisEvent['end'] += 'T' + $(this).attr('end_time'); - } - } - thisEvent['className'] = 'modal-href'; - - thisEvent['title'] = $(this).attr('title'); + + thisEvent = { + 'start': $(this).attr('earliest'), + 'end': $(this).attr('latest'), + 'className': 'modal-href', + 'title': $(this).attr('title'), + 'url': $(this).attr('url') + } + if($(this).attr('is_rig')==true || $(this).attr('status') == "Cancelled"){ thisEvent['color'] = colours[$(this).attr('status')]; }else{ thisEvent['color'] = colours['non-rig']; } - - thisEvent['url'] = $(this).attr('url'); events.push(thisEvent); }); diff --git a/RIGS/views.py b/RIGS/views.py index a0ef2785..0a085b60 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -6,9 +6,10 @@ from django.views import generic from django.db.models import Q from django.shortcuts import get_object_or_404 from django.core import serializers +from django.conf import settings import simplejson from django.contrib import messages -import datetime +import datetime, pytz import operator from registration.views import RegistrationView @@ -298,39 +299,25 @@ class SecureAPIRequest(generic.View): if model == "event" and start and end: # Probably a calendar request - start_datetime = datetime.datetime.strptime( start, "%Y-%m-%dT%H:%M:%SZ" ) - end_datetime = datetime.datetime.strptime( end, "%Y-%m-%dT%H:%M:%SZ" ) + start_datetime = datetime.datetime.strptime( start, "%Y-%m-%dT%H:%M:%S" ) + end_datetime = datetime.datetime.strptime( end, "%Y-%m-%dT%H:%M:%S" ) + + tz = pytz.timezone(settings.TIME_ZONE) + start_datetime = tz.localize(start_datetime) + objects = self.models[model].objects.events_in_bounds(start_datetime,end_datetime) results = [] for item in objects: data = { 'pk': item.pk, - 'title': item.name + 'title': item.name, + 'is_rig': item.is_rig, + 'status': str(item.get_status_display()), + 'earliest': item.earliest_time.isoformat(), + 'latest': item.latest_time.isoformat(), + 'url': str(item.get_absolute_url()) } - - data['is_rig'] = item.is_rig - data['status'] = str(item.get_status_display()) - - if item.start_date: - data['start_date'] = item.start_date.strftime('%Y-%m-%d') - - if item.has_start_time: - data['start_time'] = item.start_time.strftime('%H:%M:%SZ') - - if item.end_date: - data['end_date'] = item.end_date.strftime('%Y-%m-%d') - - if item.has_end_time: - data['end_time'] = item.end_time.strftime('%H:%M:%SZ') - - if item.meet_at: - data['meet_at'] = item.meet_at.strftime('%Y-%m-%dT%H:%M:%SZ') - - if item.access_at: - data['access_at'] = item.access_at.strftime('%Y-%m-%dT%H:%M:%SZ') - - data['url'] = str(reverse_lazy('event_detail',kwargs={'pk':item.pk})) results.append(data) json = simplejson.dumps(results) From 90ed1c9c1ddb29c065382f9b0fa42512e888293b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 29 Jun 2015 20:28:09 +0100 Subject: [PATCH 04/10] Added additional selectors to events_in_bounds --- RIGS/models.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/RIGS/models.py b/RIGS/models.py index 9515b208..6a1801a7 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -229,7 +229,14 @@ 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 + ).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic') return events From 8450c369711735351021036d617762144a347cfe Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 29 Jun 2015 21:07:10 +0100 Subject: [PATCH 05/10] Fixed some #timezoneIssues --- RIGS/models.py | 4 ++-- RIGS/templates/RIGS/calendar.html | 1 - RIGS/views.py | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/RIGS/models.py b/RIGS/models.py index 6a1801a7..95d50138 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -382,7 +382,7 @@ class Event(models.Model, RevisionMixin): startDateTime = tz.localize(startDateTime) datetime_list.append(startDateTime) # then add it to the list - earliest = min(datetime_list) #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: @@ -393,7 +393,7 @@ class Event(models.Model, RevisionMixin): @property def latest_time(self): """Returns the end of the event - this function could return either a tzaware datetime, or a naiive date object""" - + tz = pytz.timezone(settings.TIME_ZONE) endDate = self.end_date if endDate is None: endDate = self.start_date diff --git a/RIGS/templates/RIGS/calendar.html b/RIGS/templates/RIGS/calendar.html index da40a13a..0ac85749 100644 --- a/RIGS/templates/RIGS/calendar.html +++ b/RIGS/templates/RIGS/calendar.html @@ -17,7 +17,6 @@ $(document).ready(function() { $('#calendar').fullCalendar({ - //defaultDate: '2015-02-12', editable: false, eventLimit: true, // allow "more" link when too many events firstDay: 1, diff --git a/RIGS/views.py b/RIGS/views.py index 0a085b60..2c91807b 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -302,9 +302,6 @@ class SecureAPIRequest(generic.View): start_datetime = datetime.datetime.strptime( start, "%Y-%m-%dT%H:%M:%S" ) end_datetime = datetime.datetime.strptime( end, "%Y-%m-%dT%H:%M:%S" ) - tz = pytz.timezone(settings.TIME_ZONE) - start_datetime = tz.localize(start_datetime) - objects = self.models[model].objects.events_in_bounds(start_datetime,end_datetime) results = [] From 3634c7c1e4a3982250541f21d447b93eb766705d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 29 Jun 2015 21:19:18 +0100 Subject: [PATCH 06/10] Streamlined ical logic, and fixed #75, again --- RIGS/ical.py | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/RIGS/ical.py b/RIGS/ical.py index 20434c12..120e47d4 100644 --- a/RIGS/ical.py +++ b/RIGS/ical.py @@ -51,38 +51,10 @@ class CalendarICS(ICalFeed): return title def item_start_datetime(self, item): - #set start date to the earliest defined time for the event - if item.meet_at: - startDateTime = item.meet_at - elif item.access_at: - startDateTime = item.access_at - elif item.has_start_time: - startDateTime = datetime.datetime.combine(item.start_date,item.start_time) - tz = pytz.timezone(settings.TIME_ZONE) - startDateTime = tz.normalize(tz.localize(startDateTime)).astimezone(pytz.timezone(self.timezone)) - else: - startDateTime = item.start_date - - return startDateTime + return item.earliest_time def item_end_datetime(self, item): - # Assume end is same as start - endDateTime = item.start_date - - # If end date defined then use it - if item.end_date: - endDateTime = item.end_date - - if item.has_start_time and item.has_end_time: # don't allow an event with specific end but no specific start - endDateTime = datetime.datetime.combine(endDateTime,item.end_time) - tz = pytz.timezone(settings.TIME_ZONE) - endDateTime = tz.normalize(tz.localize(endDateTime)).astimezone(pytz.timezone(self.timezone)) - elif item.has_end_time: # if there's a start time specified then an end time should also be specified - endDateTime = datetime.datetime.combine(endDateTime+datetime.timedelta(days=1),datetime.time(00, 00)) - #elif item.end_time: # end time but no start time - this is weird - don't think ICS will like it so ignoring - # do nothing - - return endDateTime + return item.latest_time def item_location(self,item): return item.venue @@ -91,6 +63,8 @@ class CalendarICS(ICalFeed): # Create a nice information-rich description # note: only making use of information available to "non-keyholders" + tz = pytz.timezone(self.timezone) + desc = 'Rig ID = '+str(item.pk)+'\n' desc += 'Event = ' + item.name + '\n' desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n' @@ -102,9 +76,9 @@ class CalendarICS(ICalFeed): desc += '\n' if item.meet_at: - desc += 'Crew Meet = ' + (item.meet_at.strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n' + desc += 'Crew Meet = ' + (item.meet_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n' if item.access_at: - desc += 'Access At = ' + item.access_at.strftime('%Y-%m-%d %H:%M') + '\n' + desc += 'Access At = ' + (item.access_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.access_at else '---') + '\n' if item.start_date: desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + ((' '+item.start_time.strftime('%H:%M')) if item.has_start_time else '') + '\n' if item.end_date: @@ -113,8 +87,8 @@ class CalendarICS(ICalFeed): desc += '\n' if item.description: desc += 'Event Description:\n'+item.description+'\n\n' - if item.notes: - desc += 'Notes:\n'+item.notes+'\n\n' + # if item.notes: // Need to add proper keyholder checks before this gets put back + # desc += 'Notes:\n'+item.notes+'\n\n' base_url = "http://rigs.nottinghamtec.co.uk" desc += 'URL = '+base_url+str(item.get_absolute_url()) From 507823a26a7842d9ad765a22d7d43e51d70ad6a7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 30 Jun 2015 14:31:28 +0100 Subject: [PATCH 07/10] Added options to ical interface - issue #80 --- RIGS/ical.py | 53 +++++++++++++++++++++--- RIGS/templates/RIGS/profile_detail.html | 55 +++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/RIGS/ical.py b/RIGS/ical.py index 120e47d4..8ae94342 100644 --- a/RIGS/ical.py +++ b/RIGS/ical.py @@ -17,15 +17,58 @@ class CalendarICS(ICalFeed): timezone = settings.TIME_ZONE file_name = "rigs.ics" - def get(self, *args, **kwargs): - timezone.activate(timezone.UTC) - return super(CalendarICS, self).get(*args, **kwargs) + # Cancelled = 'cancelled' = False + # Dry Hire = 'dry-hire' = True + # Non Rig = 'non-rig' = True + # Rig = 'rig' = True + # Provisional = 'provisional' = True + # Confirmed/Booked = 'confirmed' = True + def get_object(self, request, *args, **kwargs): + params = {} - def items(self): + params['dry-hire'] = request.GET.get('dry-hire','true') == 'true' + params['non-rig'] = request.GET.get('non-rig','true') == 'true' + params['rig'] = request.GET.get('rig','true') == 'true' + + params['cancelled'] = request.GET.get('cancelled','false') == 'true' + params['provisional'] = request.GET.get('provisional','true') == 'true' + params['confirmed'] = request.GET.get('confirmed','true') == 'true' + + return params + + def description(self,params): + desc = "Calendar generated by RIGS system. This includes event types: " + ('Rig, ' if params['rig'] else '') + ('Non-rig, ' if params['non-rig'] else '') + ('Dry Hire ' if params['dry-hire'] else '') + '\n' + desc = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + ('Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '') + + return desc + + def items(self, params): #include events from up to 1 year ago start = datetime.datetime.now() - datetime.timedelta(days=365) - filter = Q(start_date__gte=start) & ~Q(status=models.Event.CANCELLED) + filter = Q(start_date__gte=start) + + typeFilters = Q(pk=None) #Need something that is false for every entry + + if params['dry-hire']: + typeFilters = typeFilters | Q(dry_hire=True, is_rig=True) + + if params['non-rig']: + typeFilters = typeFilters | Q(is_rig=False) + + if params['rig']: + typeFilters = typeFilters | Q(is_rig=True, dry_hire=False) + + statusFilters = Q(pk=None) #Need something that is false for every entry + + if params['cancelled']: + statusFilters = statusFilters | Q(status=models.Event.CANCELLED) + if params['provisional']: + statusFilters = statusFilters | Q(status=models.Event.PROVISIONAL) + if params['confirmed']: + statusFilters = statusFilters | Q(status=models.Event.CONFIRMED) | Q(status=models.Event.BOOKED) + + filter = filter & typeFilters & statusFilters return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic') diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index 33437ea4..c8c661c4 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -2,6 +2,29 @@ {% block title %}RIGS Profile {{object.pk}}{% endblock %} +{% block js %} + + +{% endblock %} + {% block content %}
@@ -68,16 +91,42 @@ {% endif %} +
Calendar Options
+
+
+
+
+ + + + + + +
+
+
+
Calendar URL
{% if user.api_key %} -
http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}
- Click here to add to google calendar.
+

+						Click here to add 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
{% endif %} -
{% endif %} From 8b5d8e60567a82cb42661a441747693dcbf9e80f Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 30 Jun 2015 15:05:35 +0100 Subject: [PATCH 08/10] Only put params in URL if not default values --- RIGS/templates/RIGS/profile_detail.html | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index c8c661c4..8fce1e60 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -8,9 +8,13 @@ $('#urlParamForm').change(function(){ url = "?"; $('#urlParamForm *').filter(':input').each(function(index, value){ + defaultVal = $(value).data('default'); param = $(value).val(); val = $(value).prop('checked'); - url = url+param+"="+val+"&"; + + if(val != defaultVal){ + url = url+param+"="+val+"&"; + } }); ics_url = $('#cal-url').data('url') + url.substring(0, url.length - 1); $('#cal-url').text(ics_url); @@ -97,22 +101,22 @@
From 278864c7ea1e3faf4c2aadf8db1bec142c76415c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 30 Jun 2015 17:11:50 +0100 Subject: [PATCH 09/10] Added ical tests, and upgraded selenium --- RIGS/test_functional.py | 191 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- 2 files changed, 192 insertions(+), 1 deletion(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index c9b5c906..69e94489 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from django.test import LiveServerTestCase +from django.test.client import Client from django.core import mail from selenium import webdriver from selenium.webdriver.common.keys import Keys @@ -8,6 +9,7 @@ from selenium.webdriver.support.ui import WebDriverWait from RIGS import models import re import os +from datetime import date, timedelta from django.db import transaction import reversion import json @@ -459,6 +461,195 @@ class EventTest(LiveServerTestCase): organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..') +class IcalTest(LiveServerTestCase): + + def setUp(self): + self.all_events = set(range(1, 18)) + self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18) + self.not_current_events = set(self.all_events) - set(self.current_events) + + self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1') + self.profile = models.Profile( + username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True) + self.profile.set_password("EventTestPassword") + self.profile.save() + + # produce 7 normal events - 5 current - 1 last week - 1 two years ago - 2 provisional - 2 confirmed - 3 booked + models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end") + models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(), description="start today no end") + models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(), end_date=date.today(), description="start today with end today") + models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED, start_date=date.today()-timedelta(weeks=104), description="start past 2 years no end") + models.Event.objects.create(name="TE E5", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=7), end_date='2014-03-21', description="start past 1 week with end past") + models.Event.objects.create(name="TE E6", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start past, end future") + models.Event.objects.create(name="TE E7", status=models.Event.BOOKED, start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start + end in future") + + # 2 cancelled - 1 current + models.Event.objects.create(name="TE E8", start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled in future") + models.Event.objects.create(name="TE E9", start_date=date.today()-timedelta(days=1), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled and started") + + # 5 dry hire - 3 current - 1 cancelled + models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today") + models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile, description="dryhire today, checked in") + models.Event.objects.create(name="TE E12", start_date=date.today()-timedelta(days=1), dry_hire=True, status=models.Event.BOOKED, description="dryhire past") + models.Event.objects.create(name="TE E13", start_date=date.today()-timedelta(days=2), dry_hire=True, checked_in_by=self.profile, description="dryhire past checked in") + models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, status=models.Event.CANCELLED, description="dryhire today cancelled") + + # 4 non rig - 3 current + models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today") + models.Event.objects.create(name="TE E16", start_date=date.today()+timedelta(days=1), is_rig=False, description="non rig tomorrow") + models.Event.objects.create(name="TE E17", start_date=date.today()-timedelta(days=1), is_rig=False, description="non rig yesterday") + models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED, description="non rig today cancelled") + + self.browser = webdriver.Firefox() + os.environ['RECAPTCHA_TESTING'] = 'True' + + def tearDown(self): + self.browser.quit() + os.environ['RECAPTCHA_TESTING'] = 'False' + + def authenticate(self, n=None): + self.assertIn( + self.live_server_url + '/user/login/', self.browser.current_url) + if n: + self.assertIn('?next=%s' % n, self.browser.current_url) + username = self.browser.find_element_by_id('id_username') + password = self.browser.find_element_by_id('id_password') + submit = self.browser.find_element_by_css_selector( + 'input[type=submit]') + + username.send_keys("EventTest") + password.send_keys("EventTestPassword") + self.browser.execute_script( + "return jQuery('#g-recaptcha-response').val('PASSED')") + submit.click() + + self.assertEqual(self.live_server_url + n, self.browser.current_url) + + def testApiKeyGeneration(self): + # Requests address + self.browser.get(self.live_server_url + '/user/') + # Gets redirected to login + self.authenticate('/user/') + + # Completes and comes back to /user/ + # Checks that no api key is displayed + self.assertEqual("No API Key Generated", self.browser.find_element_by_xpath("//div[@id='content']/div/div/div[3]/dl[2]/dd").text) + self.assertEqual("No API Key Generated", self.browser.find_element_by_css_selector("pre").text) + + # Now creates an API key, and check a URL is displayed one + self.browser.find_element_by_link_text("Generate API Key").click() + self.assertIn("rigs.ics", self.browser.find_element_by_id("cal-url").text) + self.assertNotIn("?", self.browser.find_element_by_id("cal-url").text) + + # Lets change everything so it's not the default value + self.browser.find_element_by_xpath("//input[@value='rig']").click() + self.browser.find_element_by_xpath("//input[@value='non-rig']").click() + self.browser.find_element_by_xpath("//input[@value='dry-hire']").click() + self.browser.find_element_by_xpath("//input[@value='cancelled']").click() + self.browser.find_element_by_xpath("//input[@value='provisional']").click() + self.browser.find_element_by_xpath("//input[@value='confirmed']").click() + + # and then check the url is correct + self.assertIn("rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false", self.browser.find_element_by_id("cal-url").text) + + # Awesome - all seems to work + + def testICSFiles(self): + # Requests address + self.browser.get(self.live_server_url + '/user/') + # Gets redirected to login + self.authenticate('/user/') + + # Now creates an API key, and check a URL is displayed one + self.browser.find_element_by_link_text("Generate API Key").click() + + + + c = Client() + + # Default settings - should have all non-cancelled events + # Get the ical file (can't do this in selanium because reasons) + icalUrl = self.browser.find_element_by_id("cal-url").text + response = c.get(icalUrl) + self.assertEqual(200, response.status_code) + + #Check has entire file + self.assertIn("BEGIN:VCALENDAR", response.content) + self.assertIn("END:VCALENDAR", response.content) + + expectedIn= [1,2,3,5,6,7,10,11,12,13,15,16,17] + for test in range(1,18): + if test in expectedIn: + self.assertIn("TE E"+str(test)+" ", response.content) + else: + self.assertNotIn("TE E"+str(test)+" ", response.content) + + + # Only dry hires + self.browser.find_element_by_xpath("//input[@value='rig']").click() + self.browser.find_element_by_xpath("//input[@value='non-rig']").click() + + icalUrl = self.browser.find_element_by_id("cal-url").text + response = c.get(icalUrl) + self.assertEqual(200, response.status_code) + + expectedIn= [10,11,12,13] + for test in range(1,18): + if test in expectedIn: + self.assertIn("TE E"+str(test)+" ", response.content) + else: + self.assertNotIn("TE E"+str(test)+" ", response.content) + + + # Only provisional rigs + self.browser.find_element_by_xpath("//input[@value='rig']").click() + self.browser.find_element_by_xpath("//input[@value='dry-hire']").click() + self.browser.find_element_by_xpath("//input[@value='confirmed']").click() + + icalUrl = self.browser.find_element_by_id("cal-url").text + response = c.get(icalUrl) + self.assertEqual(200, response.status_code) + + expectedIn= [1,2] + for test in range(1,18): + if test in expectedIn: + self.assertIn("TE E"+str(test)+" ", response.content) + else: + self.assertNotIn("TE E"+str(test)+" ", response.content) + + # Only cancelled non-rigs + self.browser.find_element_by_xpath("//input[@value='rig']").click() + self.browser.find_element_by_xpath("//input[@value='non-rig']").click() + self.browser.find_element_by_xpath("//input[@value='provisional']").click() + self.browser.find_element_by_xpath("//input[@value='cancelled']").click() + + icalUrl = self.browser.find_element_by_id("cal-url").text + response = c.get(icalUrl) + self.assertEqual(200, response.status_code) + + expectedIn= [18] + for test in range(1,18): + if test in expectedIn: + self.assertIn("TE E"+str(test)+" ", response.content) + else: + self.assertNotIn("TE E"+str(test)+" ", response.content) + + # Nothing selected + self.browser.find_element_by_xpath("//input[@value='non-rig']").click() + self.browser.find_element_by_xpath("//input[@value='cancelled']").click() + + icalUrl = self.browser.find_element_by_id("cal-url").text + response = c.get(icalUrl) + self.assertEqual(200, response.status_code) + + expectedIn= [] + for test in range(1,18): + if test in expectedIn: + self.assertIn("TE E"+str(test)+" ", response.content) + else: + self.assertNotIn("TE E"+str(test)+" ", response.content) + + # Wow - that was a lot of tests class animation_is_finished(object): """ Checks if animation is done """ diff --git a/requirements.txt b/requirements.txt index 5cd47409..03448bdb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ PyPDF2==1.24 python-dateutil==2.4.2 pytz==2015.4 reportlab==3.1.44 -selenium==2.45.0 +selenium==2.46.0 simplejson==3.7.2 six==1.9.0 sqlparse==0.1.15 From e8aeb6aed7e6ffdf7e46c25f119bbc6e042c215b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 29 Jul 2015 18:55:43 +0100 Subject: [PATCH 10/10] Fixed time-travelling in the functional tests --- RIGS/test_functional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 10d52149..514e030f 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -596,7 +596,7 @@ class IcalTest(LiveServerTestCase): models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(), description="start today no end") models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(), end_date=date.today(), description="start today with end today") models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED, start_date=date.today()-timedelta(weeks=104), description="start past 2 years no end") - models.Event.objects.create(name="TE E5", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=7), end_date='2014-03-21', description="start past 1 week with end past") + models.Event.objects.create(name="TE E5", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=7), end_date=date.today()-timedelta(days=1), description="start past 1 week with end past") models.Event.objects.create(name="TE E6", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start past, end future") models.Event.objects.create(name="TE E7", status=models.Event.BOOKED, start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start + end in future")