diff --git a/RIGS/ical.py b/RIGS/ical.py index 20434c12..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') @@ -51,38 +94,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 +106,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 +119,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 +130,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()) diff --git a/RIGS/models.py b/RIGS/models.py index 268f8eeb..49963a6f 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 @@ -239,7 +239,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 @@ -359,6 +366,59 @@ 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).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: + return self.start_date + + 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""" + tz = pytz.timezone(settings.TIME_ZONE) + 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 b28a0f67..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, @@ -37,75 +36,156 @@ // 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({ 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': 'orange', - 'Confirmed': 'green' , - 'Booked': 'green' , - 'Cancelled': 'grey' - }; - - 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['color'] = colours[$(this).attr('status')]; - thisEvent['url'] = $(this).attr('url'); + 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']; + } events.push(thisEvent); }); 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 %} +
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.No API Key Generated{% endif %} -