From 583b2514afb02fae5fc8c215c6dcce59308f9db1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 3 Apr 2015 20:45:58 +0100 Subject: [PATCH 01/13] Attempt using ical extension - not working --- RIGS/urls.py | 3 ++ RIGS/views.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 90 insertions(+) diff --git a/RIGS/urls.py b/RIGS/urls.py index 27c932f9..22f0ac4e 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -114,6 +114,9 @@ urlpatterns = patterns('', url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), name='profile_update_self'), + # ICS Calendar + url(r'^ics/$', (views.CalendarICS()), name="ics_calendar"), + # API url(r'^api/(?P\w+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"), url(r'^api/(?P\w+)/(?P\d+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"), diff --git a/RIGS/views.py b/RIGS/views.py index 57feffaa..56a302c2 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -8,6 +8,7 @@ from django.shortcuts import get_object_or_404 from django.core import serializers import simplejson from django.contrib import messages +from django_ical.views import ICalFeed from RIGS import models, forms @@ -263,6 +264,91 @@ class SecureAPIRequest(generic.View): return HttpResponse(model) +class CalendarICS(ICalFeed): + """ + A simple event calender + """ + product_id = '-//example.com//Example//EN' + timezone = 'UTC' + file_name = "event.ics" + + def items(self): + return models.Event.objects.all().order_by('-start_date') + + def item_title(self, item): + title = '' + if item.cancelled: + title += 'CANCELLED: ' + + if not item.is_rig: + title += 'NON-RIG: ' + + if item.dry_hire: + title += 'DRY HIRE: ' + + title += item.name + + title += ' ('+str(item.status)+')' + + return title + + def item_start_datetime(self, item): + startDateTime = item.start_date#.strftime("%Y%M%d") + + #if item.start_time: + # startDateTime += 'T'+item.start_time.strftime("%H%i") + + return startDateTime + + def item_end_datetime(self, item): + endDateTime = item.start_date.strftime("%Y%M%d") + + #if item.end_date: + # endDateTime = item.end_date.strftime("%Y%M%d") + # + #if item.start_time and item.end_time: # don't allow an event with specific end but no specific start + # endDateTime += 'T'+item.end_time.strftime("%H%i") + #elif item.start_time: # if there's a start time specified then an end time should also be specified + # endDateTime += 'T2359' + #elif item.end_time: # end time but no start time - this is weird - don't think ICS will like it so ignoring + # endDateTime += '' # do nothing + + return endDateTime + + def item_location(self,item): + return item.venue + + def item_description(self, item): + desc = 'Rig ID = '+str(item.pk)+'\n' + desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n' + desc += 'Status = ' + str(item.status) + '\n' + desc += 'Event = ' + item.name + '\n' + desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n' + if item.is_rig and item.person: + desc += 'Client = ' + item.person.name + ( ('for'+item.organisation.name) if item.organisation else '') + '\n' + desc += '\n\n' + if item.description: + desc += 'Event Description:\n'+item.description + if item.notes: + desc += 'Notes:\n'+item.notes + + + + + return item.description + + def item_link(self, item): + return '' + + # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) + # return '' + + def item_updated(self, item): + return item.last_edited_at + + def item_guid(self, item): + return item.pk + class ProfileDetail(generic.DetailView): model = models.Profile diff --git a/requirements.txt b/requirements.txt index a8c14984..2afebf22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ pillow==2.7.0 reportlab==2.7 z3c.rml==2.7.2 pyPDF2==1.23 +django-ical==1.3 \ No newline at end of file From 0ba4c8982eeb4fc5f6c96e071dda6a797599f659 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 4 Apr 2015 00:19:17 +0100 Subject: [PATCH 02/13] Working version of ics interface - not very efficient --- RIGS/urls.py | 2 +- RIGS/views.py | 74 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/RIGS/urls.py b/RIGS/urls.py index 22f0ac4e..c8f095e9 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -115,7 +115,7 @@ urlpatterns = patterns('', name='profile_update_self'), # ICS Calendar - url(r'^ics/$', (views.CalendarICS()), name="ics_calendar"), + url(r'^calendar/rigs.ics$', (views.CalendarICS()), name="ics_calendar"), # API url(r'^api/(?P\w+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"), diff --git a/RIGS/views.py b/RIGS/views.py index 56a302c2..1e0095f2 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -9,6 +9,7 @@ from django.core import serializers import simplejson from django.contrib import messages from django_ical.views import ICalFeed +import datetime from RIGS import models, forms @@ -268,12 +269,18 @@ class CalendarICS(ICalFeed): """ A simple event calender """ - product_id = '-//example.com//Example//EN' + product_id = 'PyRIGS' + title = 'PyRIGS Calendar' timezone = 'UTC' - file_name = "event.ics" + file_name = "rigs.ics" def items(self): - return models.Event.objects.all().order_by('-start_date') + #get events for today +- 1 year + start = datetime.datetime.now() - datetime.timedelta(days=1*365) + end = datetime.date.today() + datetime.timedelta(days=1*365) + filter = Q(start_date__lte=end) & Q(start_date__gte=start) + + return models.Event.objects.filter(filter).order_by('-start_date') def item_title(self, item): title = '' @@ -288,30 +295,35 @@ class CalendarICS(ICalFeed): title += item.name - title += ' ('+str(item.status)+')' + title += ' ('+str(item.get_status_display())+')' return title def item_start_datetime(self, item): - startDateTime = item.start_date#.strftime("%Y%M%d") - - #if item.start_time: - # startDateTime += 'T'+item.start_time.strftime("%H%i") + #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.start_time: + startDateTime = datetime.datetime.combine(item.start_date,item.start_time) + else: + startDateTime = item.start_date return startDateTime def item_end_datetime(self, item): - endDateTime = item.start_date.strftime("%Y%M%d") + endDateTime = item.start_date - #if item.end_date: - # endDateTime = item.end_date.strftime("%Y%M%d") - # - #if item.start_time and item.end_time: # don't allow an event with specific end but no specific start - # endDateTime += 'T'+item.end_time.strftime("%H%i") - #elif item.start_time: # if there's a start time specified then an end time should also be specified - # endDateTime += 'T2359' + if item.end_date: + endDateTime = item.end_date + + if item.start_time and item.end_time: # don't allow an event with specific end but no specific start + endDateTime = datetime.datetime.combine(endDateTime,item.end_time) + elif item.start_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 - # endDateTime += '' # do nothing + # do nothing return endDateTime @@ -320,25 +332,35 @@ class CalendarICS(ICalFeed): def item_description(self, item): desc = 'Rig ID = '+str(item.pk)+'\n' - desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n' - desc += 'Status = ' + str(item.status) + '\n' desc += 'Event = ' + item.name + '\n' desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n' if item.is_rig and item.person: - desc += 'Client = ' + item.person.name + ( ('for'+item.organisation.name) if item.organisation else '') + '\n' - desc += '\n\n' + desc += 'Client = ' + item.person.name + ( (' for '+item.organisation.name) if item.organisation else '') + '\n' + desc += 'Status = ' + str(item.get_status_display()) + '\n' + desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n' + + + desc += '\n' + if item.meet_at: + desc += 'Crew Meet = ' + item.meet_at.strftime('%Y-%m-%d %H:%M') + (('('+item.meet_info+')') if item.meet_info else '---') + '\n' + if item.access_at: + desc += 'Access At = ' + item.access_at.strftime('%Y-%m-%d %H:%M') + '\n' + if item.start_date: + desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + ((' '+item.start_time.strftime('%H:%M')) if item.start_time else '') + '\n' + if item.end_date: + desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + ((' '+item.end_time.strftime('%H:%M')) if item.end_time else '') + '\n' + + desc += '\n' if item.description: - desc += 'Event Description:\n'+item.description + desc += 'Event Description:\n'+item.description+'\n\n' if item.notes: desc += 'Notes:\n'+item.notes - - - return item.description + return desc def item_link(self, item): - return '' + return '/event/'+str(item.pk)+'/' # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) # return '' From 895b1c177e4edaf7910708d6e28fa1ee715223ca Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 18 Apr 2015 22:39:58 +0100 Subject: [PATCH 03/13] Made more efficient & split view into a separate file --- RIGS/ical.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ RIGS/urls.py | 4 +- RIGS/views.py | 107 ------------------------------------------------ 3 files changed, 113 insertions(+), 109 deletions(-) create mode 100644 RIGS/ical.py diff --git a/RIGS/ical.py b/RIGS/ical.py new file mode 100644 index 00000000..728f8485 --- /dev/null +++ b/RIGS/ical.py @@ -0,0 +1,111 @@ +from RIGS import models, forms +from django_ical.views import ICalFeed +from django.db.models import Q + +import datetime + +class CalendarICS(ICalFeed): + """ + A simple event calender + """ + product_id = 'PyRIGS' + title = 'PyRIGS Calendar' + timezone = 'UTC' + file_name = "rigs.ics" + + def items(self): + #get events for today +- 1 year + start = datetime.datetime.now() - datetime.timedelta(days=31*3) + end = datetime.date.today() + datetime.timedelta(days=31*3) + filter = Q(start_date__gte=start) + + return models.Event.objects.filter(filter).select_related('person', 'organisation', 'venue', 'mic').order_by('-start_date') + + def item_title(self, item): + title = '' + if item.cancelled: + title += 'CANCELLED: ' + + if not item.is_rig: + title += 'NON-RIG: ' + + if item.dry_hire: + title += 'DRY HIRE: ' + + title += item.name + + title += ' ('+str(item.get_status_display())+')' + + 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.start_time: + startDateTime = datetime.datetime.combine(item.start_date,item.start_time) + else: + startDateTime = item.start_date + + return startDateTime + + def item_end_datetime(self, item): + endDateTime = item.start_date + + if item.end_date: + endDateTime = item.end_date + + if item.start_time and item.end_time: # don't allow an event with specific end but no specific start + endDateTime = datetime.datetime.combine(endDateTime,item.end_time) + elif item.start_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 + + def item_location(self,item): + return item.venue + + def item_description(self, item): + desc = 'Rig ID = '+str(item.pk)+'\n' + desc += 'Event = ' + item.name + '\n' + desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n' + if item.is_rig and item.person: + desc += 'Client = ' + item.person.name + ( (' for '+item.organisation.name) if item.organisation else '') + '\n' + desc += 'Status = ' + str(item.get_status_display()) + '\n' + desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n' + + + desc += '\n' + if item.meet_at: + desc += 'Crew Meet = ' + item.meet_at.strftime('%Y-%m-%d %H:%M') + (('('+item.meet_info+')') if item.meet_info else '---') + '\n' + if item.access_at: + desc += 'Access At = ' + item.access_at.strftime('%Y-%m-%d %H:%M') + '\n' + if item.start_date: + desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + ((' '+item.start_time.strftime('%H:%M')) if item.start_time else '') + '\n' + if item.end_date: + desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + ((' '+item.end_time.strftime('%H:%M')) if item.end_time else '') + '\n' + + desc += '\n' + if item.description: + desc += 'Event Description:\n'+item.description+'\n\n' + if item.notes: + desc += 'Notes:\n'+item.notes + + + return desc + + def item_link(self, item): + return '/event/'+str(item.pk)+'/' + + # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) + # return '' + + def item_updated(self, item): + return item.last_edited_at + + def item_guid(self, item): + return item.pk \ No newline at end of file diff --git a/RIGS/urls.py b/RIGS/urls.py index c8f095e9..f25dbfa6 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import patterns, include, url from django.contrib.auth.decorators import login_required -from RIGS import views, rigboard, finance +from RIGS import views, rigboard, finance, ical from django.views.generic import RedirectView from PyRIGS.decorators import permission_required_with_403 @@ -115,7 +115,7 @@ urlpatterns = patterns('', name='profile_update_self'), # ICS Calendar - url(r'^calendar/rigs.ics$', (views.CalendarICS()), name="ics_calendar"), + url(r'^calendar/rigs.ics$', (ical.CalendarICS()), name="ics_calendar"), # API url(r'^api/(?P\w+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"), diff --git a/RIGS/views.py b/RIGS/views.py index 1e0095f2..e1f78979 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -8,7 +8,6 @@ from django.shortcuts import get_object_or_404 from django.core import serializers import simplejson from django.contrib import messages -from django_ical.views import ICalFeed import datetime from RIGS import models, forms @@ -265,112 +264,6 @@ class SecureAPIRequest(generic.View): return HttpResponse(model) -class CalendarICS(ICalFeed): - """ - A simple event calender - """ - product_id = 'PyRIGS' - title = 'PyRIGS Calendar' - timezone = 'UTC' - file_name = "rigs.ics" - - def items(self): - #get events for today +- 1 year - start = datetime.datetime.now() - datetime.timedelta(days=1*365) - end = datetime.date.today() + datetime.timedelta(days=1*365) - filter = Q(start_date__lte=end) & Q(start_date__gte=start) - - return models.Event.objects.filter(filter).order_by('-start_date') - - def item_title(self, item): - title = '' - if item.cancelled: - title += 'CANCELLED: ' - - if not item.is_rig: - title += 'NON-RIG: ' - - if item.dry_hire: - title += 'DRY HIRE: ' - - title += item.name - - title += ' ('+str(item.get_status_display())+')' - - 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.start_time: - startDateTime = datetime.datetime.combine(item.start_date,item.start_time) - else: - startDateTime = item.start_date - - return startDateTime - - def item_end_datetime(self, item): - endDateTime = item.start_date - - if item.end_date: - endDateTime = item.end_date - - if item.start_time and item.end_time: # don't allow an event with specific end but no specific start - endDateTime = datetime.datetime.combine(endDateTime,item.end_time) - elif item.start_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 - - def item_location(self,item): - return item.venue - - def item_description(self, item): - desc = 'Rig ID = '+str(item.pk)+'\n' - desc += 'Event = ' + item.name + '\n' - desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n' - if item.is_rig and item.person: - desc += 'Client = ' + item.person.name + ( (' for '+item.organisation.name) if item.organisation else '') + '\n' - desc += 'Status = ' + str(item.get_status_display()) + '\n' - desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n' - - - desc += '\n' - if item.meet_at: - desc += 'Crew Meet = ' + item.meet_at.strftime('%Y-%m-%d %H:%M') + (('('+item.meet_info+')') if item.meet_info else '---') + '\n' - if item.access_at: - desc += 'Access At = ' + item.access_at.strftime('%Y-%m-%d %H:%M') + '\n' - if item.start_date: - desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + ((' '+item.start_time.strftime('%H:%M')) if item.start_time else '') + '\n' - if item.end_date: - desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + ((' '+item.end_time.strftime('%H:%M')) if item.end_time else '') + '\n' - - desc += '\n' - if item.description: - desc += 'Event Description:\n'+item.description+'\n\n' - if item.notes: - desc += 'Notes:\n'+item.notes - - - return desc - - def item_link(self, item): - return '/event/'+str(item.pk)+'/' - - # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) - # return '' - - def item_updated(self, item): - return item.last_edited_at - - def item_guid(self, item): - return item.pk - class ProfileDetail(generic.DetailView): model = models.Profile From b7120aa8f36c9e050cc7c3a9e02b6a1558fc9321 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 18 Apr 2015 23:12:57 +0100 Subject: [PATCH 04/13] Tidied up & added comments --- RIGS/ical.py | 24 +++++++++++++++++------- RIGS/urls.py | 4 ++-- RIGS/views.py | 1 - 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/RIGS/ical.py b/RIGS/ical.py index 728f8485..6f0c72f7 100644 --- a/RIGS/ical.py +++ b/RIGS/ical.py @@ -8,21 +8,23 @@ class CalendarICS(ICalFeed): """ A simple event calender """ + #Metadata which is passed on to clients product_id = 'PyRIGS' title = 'PyRIGS Calendar' timezone = 'UTC' file_name = "rigs.ics" def items(self): - #get events for today +- 1 year - start = datetime.datetime.now() - datetime.timedelta(days=31*3) - end = datetime.date.today() + datetime.timedelta(days=31*3) + #include events from up to 1 year ago + start = datetime.datetime.now() - datetime.timedelta(days=365) filter = Q(start_date__gte=start) - return models.Event.objects.filter(filter).select_related('person', 'organisation', 'venue', 'mic').order_by('-start_date') + return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic') def item_title(self, item): title = '' + + # Prefix title with status (if it's a critical status) if item.cancelled: title += 'CANCELLED: ' @@ -32,8 +34,10 @@ class CalendarICS(ICalFeed): if item.dry_hire: title += 'DRY HIRE: ' + # Add the rig name title += item.name + # Add the status title += ' ('+str(item.get_status_display())+')' return title @@ -52,8 +56,10 @@ class CalendarICS(ICalFeed): return startDateTime 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 @@ -70,6 +76,9 @@ class CalendarICS(ICalFeed): return item.venue def item_description(self, item): + # Create a nice information-rich description + # note: only making use of information available to "non-keyholders" + desc = 'Rig ID = '+str(item.pk)+'\n' desc += 'Event = ' + item.name + '\n' desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n' @@ -99,13 +108,14 @@ class CalendarICS(ICalFeed): return desc def item_link(self, item): + # Make a link to the event in the web interface return '/event/'+str(item.pk)+'/' - # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) + # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) - not really necessary though # return '' - def item_updated(self, item): + def item_updated(self, item): # some ical clients will display this return item.last_edited_at - def item_guid(self, item): + def item_guid(self, item): # use the rig-id as the ical unique event identifier return item.pk \ No newline at end of file diff --git a/RIGS/urls.py b/RIGS/urls.py index f25dbfa6..12cc015d 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -114,8 +114,8 @@ urlpatterns = patterns('', url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), name='profile_update_self'), - # ICS Calendar - url(r'^calendar/rigs.ics$', (ical.CalendarICS()), name="ics_calendar"), + # ICS Calendar - no authentication! + url(r'^ical/rigs.ics$', (ical.CalendarICS()), name="ics_calendar"), # API url(r'^api/(?P\w+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"), diff --git a/RIGS/views.py b/RIGS/views.py index e1f78979..57feffaa 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -8,7 +8,6 @@ from django.shortcuts import get_object_or_404 from django.core import serializers import simplejson from django.contrib import messages -import datetime from RIGS import models, forms From f4ffb6f256c88f98cf630992ce6080d21bb83fcb Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 19 Apr 2015 17:06:03 +0100 Subject: [PATCH 05/13] Added api key stuff to profile model --- RIGS/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/RIGS/models.py b/RIGS/models.py index f31ff77d..79736131 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -7,6 +7,8 @@ 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 random from decimal import Decimal @@ -14,6 +16,14 @@ from decimal import Decimal 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=False,editable=True,default=lambda: make_api_key()) + + @classmethod + def make_api_key(cls): + size=20 + chars=string.ascii_letters + string.digits + new_api_key = ''.join(random.choice(chars) for x in range(size)) + return new_api_key; @property def profile_picture(self): @@ -26,7 +36,6 @@ class Profile(AbstractUser): def name(self): return self.get_full_name() + ' "' + self.initials + '"' - class RevisionMixin(object): @property def last_edited_at(self): From f1e508827105a895acc9c657798414c240b2855b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 19 Apr 2015 17:06:52 +0100 Subject: [PATCH 06/13] Add interface to view API key --- RIGS/templates/RIGS/profile_detail.html | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index a9d9bbaf..56a81fba 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -16,7 +16,7 @@ {% endif %} -
+
First Name
{{object.first_name}}
@@ -39,10 +39,30 @@
Phone
{{object.phone}}
+ {% if object.pk == user.pk %} + + + +

Personal iCal Details

+ +
+
API Key
+
{{user.api_key}}
+ +
Calendar URL
+
https://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}
+
+ {% endif %}
-
+ +
+
{% endblock %} \ No newline at end of file From 065fc6727fa57b6d88a996b40812f761a88150ea Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 19 Apr 2015 17:07:28 +0100 Subject: [PATCH 07/13] Added authentication decorator & reset key functionality --- RIGS/urls.py | 6 ++++-- RIGS/views.py | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/RIGS/urls.py b/RIGS/urls.py index 12cc015d..3b3937b4 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -4,6 +4,7 @@ from RIGS import views, rigboard, finance, ical from django.views.generic import RedirectView from PyRIGS.decorators import permission_required_with_403 +from PyRIGS.decorators import api_key_required urlpatterns = patterns('', # Examples: @@ -113,9 +114,10 @@ urlpatterns = patterns('', name='profile_detail'), url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), name='profile_update_self'), + url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'), - # ICS Calendar - no authentication! - url(r'^ical/rigs.ics$', (ical.CalendarICS()), name="ics_calendar"), + # ICS Calendar - API key authentication + url(r'^ical/(?P\d+)/(?P\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"), # API url(r'^api/(?P\w+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"), diff --git a/RIGS/views.py b/RIGS/views.py index 57feffaa..dd4176a2 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -287,4 +287,12 @@ class ProfileUpdateSelf(generic.UpdateView): def get_success_url(self): url = reverse_lazy('profile_detail') - return url \ No newline at end of file + return url + +class ResetApiKey(generic.RedirectView): + def get_redirect_url(self, *args, **kwargs): + self.request.user.api_key = self.request.user.make_api_key() + + self.request.user.save() + + return reverse_lazy('profile_detail') \ No newline at end of file From d0c44c921faedeb43c17b7f581ee45ad1fe6da41 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 19 Apr 2015 17:08:16 +0100 Subject: [PATCH 08/13] Actually added authentication decorator this time --- PyRIGS/decorators.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 5e4e613d..a7c1db90 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -33,4 +33,35 @@ def permission_required_with_403(perm, login_url=None): Decorator for views that checks whether a user has a particular permission enabled, redirecting to the log-in page or rendering a 403 as necessary. """ - return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url) \ No newline at end of file + return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url) + +from RIGS import models + +def api_key_required(function): + """ + Decorator for views that checks api_pk and api_key. + Failed users will be given a 403 error. + Should only be used for urls which include and kwargs + """ + def wrap(request, *args, **kwargs): + + userid = kwargs.get('api_pk') + key = kwargs.get('api_key') + + error_resp = render_to_response('403.html', context_instance=RequestContext(request)) + error_resp.status_code = 403 + + if key is None: + return error_resp + if userid is None: + return error_resp + + try: + user_object = models.Profile.objects.get(pk=userid) + except Profile.DoesNotExist: + return error_resp + + if user_object.api_key != key: + return error_resp + return function(request, *args, **kwargs) + return wrap \ No newline at end of file From 9f902eabb6e56453fa360b8963e98d826d150a9b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 19 Apr 2015 17:28:22 +0100 Subject: [PATCH 09/13] Made it work for users that already exist in database --- RIGS/models.py | 4 ++-- RIGS/templates/RIGS/profile_detail.html | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/RIGS/models.py b/RIGS/models.py index 79736131..09a70d51 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -16,8 +16,8 @@ from decimal import Decimal 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=False,editable=True,default=lambda: make_api_key()) - + api_key = models.CharField(max_length=40,blank=True,editable=False, null=True) + @classmethod def make_api_key(cls): size=20 diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index 56a81fba..1e7174f6 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -43,7 +43,8 @@ @@ -51,11 +52,24 @@
API Key
-
{{user.api_key}}
+
+ {% if user.api_key %} + {{user.api_key}} + {% else %} + No API Key Generated + {% endif %} +
Calendar URL
-
https://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}
+
+ {% if user.api_key %} +
https://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}
+ {% else %} +
No API Key Generated
+ {% endif %} +
+ {% endif %}
From d60d6b66d0b89a5723bda74cd9c243344052ce67 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 20 Apr 2015 12:57:42 +0100 Subject: [PATCH 10/13] Added api_key database migrations. Also includes other outstanding migrations: - Change Meta options on invoice - Add field api_key to profile - Alter field collector on event - Alter field initials on profile --- RIGS/migrations/0021_auto_20150420_1155.py | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 RIGS/migrations/0021_auto_20150420_1155.py diff --git a/RIGS/migrations/0021_auto_20150420_1155.py b/RIGS/migrations/0021_auto_20150420_1155.py new file mode 100644 index 00000000..269f9bc1 --- /dev/null +++ b/RIGS/migrations/0021_auto_20150420_1155.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0020_auto_20150303_0243'), + ] + + operations = [ + migrations.AlterModelOptions( + name='invoice', + options={'ordering': ['-invoice_date'], 'permissions': (('view_invoice', 'Can view Invoices'),)}, + ), + migrations.AddField( + model_name='profile', + name='api_key', + field=models.CharField(max_length=40, null=True, editable=False, blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='event', + name='collector', + field=models.CharField(max_length=255, null=True, verbose_name=b'Collected By', blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='profile', + name='initials', + field=models.CharField(max_length=5, unique=True, null=True), + preserve_default=True, + ), + ] From ad2ed85ad6f5a4bcce49e70e144515d08c0e0026 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 20 Apr 2015 15:43:02 +0100 Subject: [PATCH 11/13] Add url to description --- RIGS/ical.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/RIGS/ical.py b/RIGS/ical.py index 6f0c72f7..55362d75 100644 --- a/RIGS/ical.py +++ b/RIGS/ical.py @@ -1,6 +1,7 @@ from RIGS import models, forms from django_ical.views import ICalFeed from django.db.models import Q +from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch import datetime @@ -102,14 +103,17 @@ class CalendarICS(ICalFeed): if item.description: desc += 'Event Description:\n'+item.description+'\n\n' if item.notes: - desc += 'Notes:\n'+item.notes + desc += 'Notes:\n'+item.notes+'\n\n' + base_url = "https://pyrigs.nottinghamtec.co.uk" + desc += 'URL = '+base_url+str(reverse_lazy('event_detail',kwargs={'pk':item.pk})) return desc def item_link(self, item): # Make a link to the event in the web interface - return '/event/'+str(item.pk)+'/' + base_url = "https://pyrigs.nottinghamtec.co.uk" + return base_url+str(reverse_lazy('event_detail',kwargs={'pk':item.pk})) # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) - not really necessary though # return '' From c7b0ca4334a6b22b981e9884467983361aad9625 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 21 Apr 2015 19:25:46 +0100 Subject: [PATCH 12/13] Change ical to use dynamically generated URL's instead of a static base URL. --- RIGS/ical.py | 4 ++-- RIGS/models.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/RIGS/ical.py b/RIGS/ical.py index 55362d75..944d03a9 100644 --- a/RIGS/ical.py +++ b/RIGS/ical.py @@ -112,8 +112,8 @@ class CalendarICS(ICalFeed): def item_link(self, item): # Make a link to the event in the web interface - base_url = "https://pyrigs.nottinghamtec.co.uk" - return base_url+str(reverse_lazy('event_detail',kwargs={'pk':item.pk})) + # base_url = "https://pyrigs.nottinghamtec.co.uk" + return item.get_absolute_url() # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) - not really necessary though # return '' diff --git a/RIGS/models.py b/RIGS/models.py index d7305530..641aeab9 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -9,6 +9,7 @@ from django.utils.encoding import python_2_unicode_compatible import reversion import string import random +from django.core.urlresolvers import reverse_lazy from decimal import Decimal @@ -304,6 +305,9 @@ class Event(models.Model, RevisionMixin): objects = EventManager() + def get_absolute_url(self): + return reverse_lazy('event_detail', kwargs={'pk': self.pk}) + def __str__(self): return str(self.pk) + ": " + self.name From 98f0005d6775651d5d35b18f871087e965db207e Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 21 Apr 2015 19:31:33 +0100 Subject: [PATCH 13/13] Add checking URL is secure before using HTTPS for ical requests. In practice, this will always be yes. --- 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 1e7174f6..c40edfed 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -63,7 +63,7 @@
Calendar URL
{% if user.api_key %} -
https://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}
+
http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}
{% else %}
No API Key Generated
{% endif %}