From fae1ca2519478c979675abfa594ed0464b971263 Mon Sep 17 00:00:00 2001
From: ingo <ingo.leindecker@fro.at>
Date: Thu, 2 Nov 2017 18:29:08 +0100
Subject: [PATCH] * Fixed ending dates for business days and even/odd calendar
 weeks * Fixed some minor bugs * Added JSON view for loading a timeslot into
 the calendar See #8

---
 program/admin.py                  |  7 +--
 program/models.py                 | 75 ++++++++++++++++---------------
 program/templates/calendar.html   | 30 ++++++++++++-
 program/templates/collisions.html |  4 +-
 program/views.py                  | 15 ++++++-
 pv/urls.py                        |  4 +-
 6 files changed, 87 insertions(+), 48 deletions(-)

diff --git a/program/admin.py b/program/admin.py
index db186ff7..e84d76fb 100644
--- a/program/admin.py
+++ b/program/admin.py
@@ -8,7 +8,6 @@ from .forms import MusicFocusForm, CollisionForm
 
 from datetime import date, datetime, time, timedelta
 
-
 class ActivityFilter(admin.SimpleListFilter):
     title = _("Activity")
 
@@ -106,10 +105,6 @@ class NoteAdmin(admin.ModelAdmin):
         obj.save()
 
 
-class TimeSlotAdmin(admin.ModelAdmin):
-    model = TimeSlot
-    template_name = 'calendar.html'
-
 class TimeSlotInline(admin.TabularInline):
     model = TimeSlot
     ordering = ('-end',)
@@ -405,7 +400,7 @@ class ShowAdmin(admin.ModelAdmin):
         self.programslot = programslot
         self.timeslots = timeslots
         self.collisions = collisions
-        self.num_collisions = len([ s for s in self.collisions if s != None]) # Number of real collisions displayed to the user
+        self.num_collisions = len([ s for s in self.collisions if s != 'None']) # Number of real collisions displayed to the user
         self.notes = notes
         self.showform = form
         self.programslotsform = formset
diff --git a/program/models.py b/program/models.py
index aa6e6b30..7eda390a 100644
--- a/program/models.py
+++ b/program/models.py
@@ -281,7 +281,7 @@ class RRule(models.Model):
         (5, _("Fifth")),
         (-1, _("Last")),
     )
-    name = models.CharField(_("Name"), max_length=32, unique=True) # ,default=1
+    name = models.CharField(_("Name"), max_length=32, unique=True)
     freq = models.IntegerField(_("Frequency"), choices=FREQ_CHOICES)
     interval = models.IntegerField(_("Interval"), default=1)
     bysetpos = models.IntegerField(_("Set position"), blank=True,
@@ -344,58 +344,60 @@ class ProgramSlot(models.Model):
     def generate_timeslots(programslot):
         """
         Returns a list of timeslot objects based on a programslot and its rrule
-        Returns past timeslots starting from dstart (not today)
-
-          TODO for ENDING TIMES:
-
-          1. If rrule is 'on even or odd calendar weeks' and a timeslot lasts from SUN 23:00 - MON 00:00,
-          the end date will be in the next week, which is excluded by byweekno. Thus the end date will be in the week _after next_.
-
-          2. If rrule is 'on business days' and a timeslot lasts from FRI 23:00 - SAT 00:00,
-          the end date will be on a weekday, which is excluded by byweekday_end (and actually returns the next week-1day, which is quite unexpected)
-
-          Solution:
-            - Correct those end-dates or find a better way to formulate rrule()
-              - Either determine byweekno for ends beforehand for that case (when byweekday_end is determined)
-              - Check if combinations of dend, byweekday_end, byweekno (or interval and bysetpos) might have something to do with the problem
+        Returns past timeslots as well starting from dstart (not today)
         """
 
         byweekno = None
+        byweekno_end = None
         byweekday_end = int(programslot.byweekday)
         starts = []
         ends = []
         timeslots = []
 
+        # Handle ending weekday for timeslots over midnight
+        if programslot.tend < programslot.tstart:
+            if programslot.byweekday < 6:
+                byweekday_end = int(programslot.byweekday + 1)
+            else:
+                byweekday_end = 0
+
+        # Handle ending dates for timeslots over midnight
+        if programslot.tend < programslot.tstart:
+            dend = programslot.dstart + timedelta(days=+1)
+        else:
+            dend = programslot.dstart
+
         if programslot.rrule.freq == 0: # Ignore weekdays for one-time timeslots
             byweekday_start = None
             byweekday_end = None
         elif programslot.rrule.freq == 3 and programslot.rrule.pk == 2: # Daily timeslots
             byweekday_start = (0, 1, 2, 3, 4, 5, 6)
             byweekday_end = (0, 1, 2, 3, 4, 5, 6)
-        elif programslot.rrule.freq == 3 and programslot.rrule.pk == 3: # Business days MO - FR
+        elif programslot.rrule.freq == 3 and programslot.rrule.pk == 3: # Business days MO - FR/SA
             byweekday_start = (0, 1, 2, 3, 4)
-            byweekday_end = (0, 1, 2, 3, 4)
+            if programslot.tend < programslot.tstart:
+                # End days for over midnight
+                byweekday_end = (1, 2, 3, 4, 5)
+            else:
+                byweekday_end = (0, 1, 2, 3, 4)
         elif programslot.rrule.freq == 2 and programslot.rrule.pk == 7: # Even calendar weeks
             byweekday_start = int(programslot.byweekday)
             byweekno = list(range(2, 54, 2))
+            # Reverse ending weeks if from Sun - Mon
+            if byweekday_start == 6 and byweekday_end == 0:
+                byweekno_end = list(range(1, 54, 2))
+            else:
+                byweekno_end = byweekno
         elif programslot.rrule.freq == 2 and programslot.rrule.pk == 8: # Odd calendar weeks
             byweekday_start = int(programslot.byweekday)
             byweekno = list(range(1, 54, 2))
-        else:
-            byweekday_start = int(programslot.byweekday)
-
-        # Handle ending weekday for timeslots over midnight
-        if programslot.tend < programslot.tstart:
-            if programslot.byweekday < 6:
-                byweekday_end = int(programslot.byweekday + 1)
+            # Reverse ending weeks if from Sun - Mon
+            if byweekday_start == 6 and byweekday_end == 0:
+                byweekno_end = list(range(2, 54, 2))
             else:
-                byweekday_end = 0
-
-        # Handle ending dates for timeslots over midnight
-        if programslot.tend < programslot.tstart:
-            dend = programslot.dstart + timedelta(days=+1)
+                byweekno_end = byweekno
         else:
-            dend = programslot.dstart
+            byweekday_start = int(programslot.byweekday)
 
         if programslot.rrule.freq == 0:
             starts.append(datetime.combine(programslot.dstart, programslot.tstart))
@@ -409,16 +411,18 @@ class ProgramSlot(models.Model):
                             bysetpos=programslot.rrule.bysetpos,
                             byweekday=byweekday_start,
                             byweekno=byweekno))
+
             ends = list(rrule(freq=programslot.rrule.freq,
                           dtstart=datetime.combine(dend, programslot.tend),
                           interval=programslot.rrule.interval,
                           until=programslot.until + relativedelta(days=+1),
                           bysetpos=programslot.rrule.bysetpos,
                           byweekday=byweekday_end,
-                          byweekno=byweekno))
+                          byweekno=byweekno_end))
 
-            for k in range(min(len(starts), len(ends))):
-                timeslots.append(TimeSlot(programslot=programslot, start=starts[k], end=ends[k]).generate())
+        for k in range(min(len(starts), len(ends))):
+            timeslots.append(TimeSlot(programslot=programslot, start=starts[k], end=ends[k]).generate())
+            print(str(starts[k]) + ' - ' + str(ends[k]))
 
         return timeslots
 
@@ -441,7 +445,7 @@ class ProgramSlot(models.Model):
                            ( Q(start__lte=ts.start) & Q(end__gte=ts.end) )
                         )
 
-            if(collision):
+            if collision:
                 collisions.append(collision[0]) # TODO: Do we really always retrieve one?
             else:
                 collisions.append(None)
@@ -509,7 +513,8 @@ class TimeSlotManager(models.Manager):
 
     @staticmethod
     def get_7d_timeslots(start):
-        end = start + timedelta(hours=168)
+        start = datetime.combine(start, time(0, 0))
+        end = start + timedelta(days=7)
 
         return TimeSlot.objects.filter(Q(start__lte=start, end__gte=start) |
                                        Q(start__gt=start, start__lt=end)).exclude(end=start)
diff --git a/program/templates/calendar.html b/program/templates/calendar.html
index 58687140..2acfbaa9 100644
--- a/program/templates/calendar.html
+++ b/program/templates/calendar.html
@@ -74,7 +74,7 @@
 <div id="notification"></div>
 
 <div id="container">
-
+  {% csrf_token %}
   <div id="header">Kalender</div>
   <div class="breadcrumb"></div>
 
@@ -83,8 +83,13 @@
     <h1></h1>
 
     <div class="calendar-container">
-      <div id="sidebar">&nbsp;</div>
       <div id="calendar"></div>
+      <div id="sidebar">
+        <div id="timeslot-id"></div>
+        <div id="timeslot-start"></div>
+        <div id="timeslot-end"></div>
+        <div id="response-message"></div>
+      </div>
     </div>
 
   </div>
@@ -151,8 +156,29 @@
              jQuery(this).find('.closeon').hide();
           },
           // Triggered when an event was clicked
+          // Load the timeslot into the sidebar form
           eventClick: function(calEvent, jsEvent, view) {
           	console.log(calEvent);
+            jQuery.ajax({
+              url: '/export/get_timeslot',
+              type: 'GET',
+              data: {
+                'timeslot_id': calEvent.id,
+                'csrfmiddlewaretoken': jQuery('input[name="csrfmiddlewartetoken"]').val()
+              },
+              success: function(timeslot) {
+                jQuery("#timeslot-id").html(timeslot.id);
+                jQuery("#timeslot-start").html(timeslot.start);
+                jQuery("#timeslot-end").html(timeslot.end);
+                jQuery("#response-message").html("Success");
+              },
+              error: function() {
+                jQuery("#response-message").html("An error occured");
+              }
+
+            });
+
+
           },
           // How is this callback triggered?
           select: function( start, end, jsEvent, view ) {
diff --git a/program/templates/collisions.html b/program/templates/collisions.html
index 6881962f..c6ac173c 100644
--- a/program/templates/collisions.html
+++ b/program/templates/collisions.html
@@ -155,8 +155,8 @@
         </div>
 
         <div style="display:none;">
-         {{ programslotsform }}
-         {{ showform }}
+         {{ programslotsform.as_ul }}
+         {{ showform.as_ul }}
         </div>
 
       </form>
diff --git a/program/views.py b/program/views.py
index e2badc0b..1028f785 100644
--- a/program/views.py
+++ b/program/views.py
@@ -2,11 +2,14 @@ import json
 from datetime import date, datetime, time, timedelta
 
 from django.db.models import Q
-from django.http import HttpResponse
+from django.core.exceptions import ObjectDoesNotExist
+from django.forms.models import model_to_dict
+from django.http import HttpResponse, JsonResponse
 from django.shortcuts import get_object_or_404
 from django.views.generic.base import TemplateView
 from django.views.generic.detail import DetailView
 from django.views.generic.list import ListView
+from pprint import pprint
 
 from .models import BroadcastFormat, MusicFocus, Note, Show, ShowInformation, ShowTopic, TimeSlot, Host
 
@@ -277,4 +280,12 @@ def json_timeslots_specials(request):
         specials[automation_id]['pv_end'] = end
 
     return HttpResponse(json.dumps(specials, ensure_ascii=False).encode('utf8'),
-                        content_type="application/json; charset=utf-8")
\ No newline at end of file
+                        content_type="application/json; charset=utf-8")
+
+
+def json_get_timeslot(request):
+   if request.method == 'GET':
+      try:
+         return JsonResponse( model_to_dict(TimeSlot.objects.select_related('programslot').select_related('show').get(pk=int(request.GET.get('timeslot_id')))))
+      except ObjectDoesNotExist:
+         return JsonResponse( list('Error') );
\ No newline at end of file
diff --git a/pv/urls.py b/pv/urls.py
index 5bd8dfca..81359108 100644
--- a/pv/urls.py
+++ b/pv/urls.py
@@ -3,7 +3,8 @@ from django.conf.urls import url, include
 from django.contrib import admin
 from django.views.static import serve
 
-from program.views import json_day_schedule, json_week_schedule, json_timeslots_specials
+from program.views import json_day_schedule, json_week_schedule, json_timeslots_specials, json_get_timeslot
+
 
 admin.autodiscover()
 
@@ -15,6 +16,7 @@ urlpatterns = [
     url(r'^export/day_schedule/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$', json_day_schedule),
     url(r'^export/week_schedule$', json_week_schedule),
     url(r'^export/timeslots_specials.json$', json_timeslots_specials),
+    url(r'^export/get_timeslot$', json_get_timeslot, name='get-timeslot'),
 ]
 
 if settings.DEBUG:
-- 
GitLab