From 7bfda78e91c85097d4de1dd6720bb72f101ae892 Mon Sep 17 00:00:00 2001 From: ingo <ingo.leindecker@fro.at> Date: Wed, 14 Mar 2018 18:55:38 +0100 Subject: [PATCH] Extended rrule wrapper * for adding a number of (business) days to the schedule (e.g. "on the rrules' following day") * fixed a bug for monthly recurrences * and some minor changes See #8 --- README.rst | 7 ++- program/admin.py | 24 ++++---- program/migrations/0017_auto_20180314_1409.py | 25 +++++++++ program/models.py | 56 ++++++++++++++++++- program/serializers.py | 2 + program/templates/boxes/broadcastformat.html | 11 ---- program/templates/collisions.html | 4 +- program/templatetags/content_boxes.py | 6 +- program/views.py | 10 ++-- pv/settings.py | 3 + 10 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 program/migrations/0017_auto_20180314_1409.py delete mode 100644 program/templates/boxes/broadcastformat.html diff --git a/README.rst b/README.rst index 0302391c..b03edc78 100644 --- a/README.rst +++ b/README.rst @@ -42,10 +42,9 @@ Setting up the database By default the project is set up to run on a SQLite database. -Create a file pv/local_settings.py and add at least the two lines:: +Create a file pv/local_settings.py and add at least the following line:: SECRET_KEY = 'secret key' - USE_TZ = False (obviously replacing "secret key" with a key of your choice). @@ -54,6 +53,10 @@ Then run:: (python)$ python manage.py migrate (python)$ python manage.py loaddata program/fixtures/*.yaml +Open pv/local_settings.py again and add the line:: + + USE_TZ = False + Setting up MySQL ---------------- diff --git a/program/admin.py b/program/admin.py index b72afd8b..3eeb13fd 100644 --- a/program/admin.py +++ b/program/admin.py @@ -404,6 +404,8 @@ class ShowAdmin(admin.ModelAdmin): is_repetition = request.POST.get('ps_save_is_repetition') automation_id = int(request.POST.get('ps_save_automation_id')) if request.POST.get('ps_save_automation_id') != 'None' else 0 fallback_id = int(request.POST.get('ps_save_fallback_id')) if request.POST.get('ps_save_fallback_id') != 'None' else 0 + add_days_no = int(request.POST.get('ps_save_add_days_no')) if request.POST.get('ps_save_add_days_no') != 'None' and int(request.POST.get('ps_save_add_days_no')) > 0 else None + add_business_days_only = request.POST.get('ps_save_add_business_days_only') # Put timeslot POST vars into lists with same indices for i in range(num_inputs): @@ -439,16 +441,18 @@ class ShowAdmin(admin.ModelAdmin): '''Save schedule''' new_schedule = Schedule(pk=schedule_id, - rrule=rrule, - byweekday=byweekday, - show=show, - dstart=dstart, - tstart=tstart, - tend=tend, - until=until, - is_repetition=is_repetition, - automation_id=automation_id, - fallback_id=fallback_id) + rrule=rrule, + byweekday=byweekday, + show=show, + dstart=dstart, + tstart=tstart, + tend=tend, + until=until, + is_repetition=is_repetition, + automation_id=automation_id, + fallback_id=fallback_id, + add_days_no=add_days_no, + add_business_days_only=add_business_days_only) # Only save schedule if any timeslots changed if len(resolved_timeslots) > 0: diff --git a/program/migrations/0017_auto_20180314_1409.py b/program/migrations/0017_auto_20180314_1409.py new file mode 100644 index 00000000..2dc44cba --- /dev/null +++ b/program/migrations/0017_auto_20180314_1409.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2018-03-14 14:09 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('program', '0016_auto_20180222_1253'), + ] + + operations = [ + migrations.AddField( + model_name='schedule', + name='add_business_days_only', + field=models.BooleanField(default=False, verbose_name='Only add business days?'), + ), + migrations.AddField( + model_name='schedule', + name='add_days_no', + field=models.IntegerField(blank=True, null=True, verbose_name='Add days'), + ), + ] diff --git a/program/models.py b/program/models.py index 4a3fe716..486b3fb1 100644 --- a/program/models.py +++ b/program/models.py @@ -407,6 +407,8 @@ class Schedule(models.Model): tend = models.TimeField(_("End time")) until = models.DateField(_("Last date")) is_repetition = models.BooleanField(_("Is repetition"), default=False) + add_days_no = models.IntegerField(_("Add days"), blank=True, null=True) + add_business_days_only = models.BooleanField(_("Only add business days?"), default=False) fallback_id = models.IntegerField(_("Fallback ID"), blank=True, null=True) automation_id = models.IntegerField(_("Automation ID"), blank=True, null=True, choices=get_automation_id_choices()) # Deprecated created = models.DateTimeField(auto_now_add=True, editable=False, null=True) #-> both see https://stackoverflow.com/questions/1737017/django-auto-now-and-auto-now-add @@ -440,6 +442,8 @@ class Schedule(models.Model): is_repetition = True if 'is_repetition' in sdl and sdl['is_repetition'] == 'true' else False fallback_id = int(sdl['fallback_id']) if sdl['fallback_id'] else None automation_id = int(sdl['automation_id']) if sdl['automation_id'] else None + add_days_no = int(sdl['add_days_no']) if sdl['add_days_no'] > 0 else None + add_business_days_only = True if sdl['add_business_days_only'] == 'true' else False dstart = datetime.strptime(str(sdl['dstart']), '%Y-%m-%d').date() @@ -468,7 +472,8 @@ class Schedule(models.Model): schedule = Schedule(pk=pk, byweekday=sdl['byweekday'], rrule=rrule, dstart=dstart, tstart=tstart, tend=tend, until=until, is_repetition=is_repetition, - fallback_id=fallback_id, show=show) + fallback_id=fallback_id, show=show, + add_days_no=add_days_no, add_business_days_only=add_business_days_only) return schedule @@ -553,6 +558,52 @@ class Schedule(models.Model): byweekno=byweekno_end)) for k in range(min(len(starts), len(ends))): + + # Correct dates for the (relatively seldom) case if: + # E.g.: 1st Monday from 23:00:00 to 1st Tuesday 00:00:00 + # produces wrong end dates if the 1st Tuesday is before the 1st Monday + # In this case we take the next day instead of rrule's calculated end + if starts[k] > ends[k]: + ends[k] = datetime.combine(starts[k] + relativedelta(days=+1), schedule.tend) + + + ''' + Add a number of days to the generated dates? + + This can be helpful for repetitions: + + Examples: + + 1. If RRule is "Every 1st Monday" and we want its repetition alyways to be on the following day, + the repetition's RRule is the same but add_days_no is 1 + + If we would set the repetition to "Every 1st Tuesday" instead + we will get unmeant results if the 1st Tuesday is before the 1st Monday + (e.g. 1st Tue = May 1 2018, 1st Mon = May 7 2018) + + 2. If RRule is "Every 1st Friday" and we want its repetition always to be on the following business day, + the repetition's RRule is the same but add_days_no is 1 and add_business_days_only is True + (e.g. original date = Fri, March 2 2018; generated date = Mon, March 5 2018) + + In the UI these can be presets: + "On the following day" (add_days_no=1,add_business_days_only=False) or + "On the following business day" (add_days_no=1,add_business_days_only=True) + + ''' + if schedule.add_days_no != None and schedule.add_days_no > 0: + # If only business days and weekday is Fri, Sat or Sun: add add_days_no beginning from Sunday + weekday = datetime.date(starts[k]).weekday() + if schedule.add_business_days_only and weekday > 3: + days_until_sunday = 6 - weekday + starts[k] = starts[k] + relativedelta(days=+days_until_sunday+schedule.add_days_no) + ends[k] = ends[k] + relativedelta(days=+days_until_sunday+schedule.add_days_no) + else: + starts[k] = starts[k] + relativedelta(days=+schedule.add_days_no) + ends[k] = ends[k] + relativedelta(days=+schedule.add_days_no) + + if ends[k].date() > schedule.until: + schedule.until = ends[k].date() + timeslots.append(TimeSlot(schedule=schedule, start=starts[k], end=ends[k]).generate()) return timeslots @@ -576,7 +627,10 @@ class Schedule(models.Model): ( Q(start__lte=ts.start) & Q(end__gte=ts.end) ) ) + print("testing " + str(ts.start) + " - " + str(ts.end)) + if collision: + print("collision found: " + str(vars(collision[0]))) collisions.append(collision[0]) # TODO: Do we really always retrieve one? else: collisions.append(None) diff --git a/program/serializers.py b/program/serializers.py index 696f8eed..0a5cb212 100644 --- a/program/serializers.py +++ b/program/serializers.py @@ -353,6 +353,8 @@ class ScheduleSerializer(serializers.ModelSerializer): instance.automation_id = validated_data.get('automation_id', instance.automation_id) instance.rrule = validated_data.get('rrule', instance.rrule) instance.show = validated_data.get('show', instance.show) + instance.add_days_no = validated_data.get('add_days_no', instance.add_days_no) + instance.add_business_days_only = validated_data.get('add_business_days_only', instance.add_business_days_only) instance.save() return instance diff --git a/program/templates/boxes/broadcastformat.html b/program/templates/boxes/broadcastformat.html deleted file mode 100644 index 8118cdb9..00000000 --- a/program/templates/boxes/broadcastformat.html +++ /dev/null @@ -1,11 +0,0 @@ -{% if broadcastformat_list %} - <dl id="broadcastformat" class="portlet"> - <dt class="portletHeader"><span>Legende</span></dt> - {% for bf in broadcastformat_list %} - <dd class="portletItem bcformat bf-{{ bf.slug }}"> - <a title="Sendungen mit dem Sendungsformat {{ bf.format }} anzeigen." - href="?broadcastformat={{ bf.slug }}">{{ bf.format }}</a> - </dd> - {% endfor %} - </dl> -{% endif %} diff --git a/program/templates/collisions.html b/program/templates/collisions.html index 24836912..c2ad336e 100644 --- a/program/templates/collisions.html +++ b/program/templates/collisions.html @@ -137,7 +137,9 @@ <input type="hidden" name="ps_save_is_repetition" value="{{ schedule.is_repetition }}" /> <input type="hidden" name="ps_save_automation_id" value="{{ schedule.automation_id }}" /> <input type="hidden" name="ps_save_fallback_id" value="{{ schedule.fallback_id }}" /> - <input type="hidden" name="ps_save_show_id" value="{{ schedule.show_id }} " /> + <input type="hidden" name="ps_save_show_id" value="{{ schedule.show_id }}" /> + <input type="hidden" name="ps_save_add_days_no" value="{{ schedule.add_days_no }}" /> + <input type="hidden" name="ps_save_add_business_days_only" value="{{ schedule.add_business_days_only }}" /> <input type="hidden" name="num_inputs" value="{{ num_inputs }}" /> <input type="hidden" name="step" value="{{ step }}" /> diff --git a/program/templatetags/content_boxes.py b/program/templatetags/content_boxes.py index 73fa691a..6f4caa32 100644 --- a/program/templatetags/content_boxes.py +++ b/program/templatetags/content_boxes.py @@ -14,14 +14,14 @@ def type(): @register.inclusion_tag('boxes/musicfocus.html') def musicfocus(): - return {'musicfocus_list': MusicFocus.objects.all()} + return {'musicfocus_list': MusicFocus.objects.filter(is_active=True)} @register.inclusion_tag('boxes/category.html') def category(): - return {'category_list': Category.objects.all()} + return {'category_list': Category.objects.filter(is_active=True)} @register.inclusion_tag('boxes/topic.html') def topic(): - return {'topic_list': Topic.objects.all()} \ No newline at end of file + return {'topic_list': Topic.objects.filter(is_active=True)} \ No newline at end of file diff --git a/program/views.py b/program/views.py index 7bebcfb4..538512ce 100644 --- a/program/views.py +++ b/program/views.py @@ -22,7 +22,6 @@ from program.models import Type, MusicFocus, Language, Note, Show, Category, Fun from program.serializers import TypeSerializer, LanguageSerializer, MusicFocusSerializer, NoteSerializer, ShowSerializer, ScheduleSerializer, CategorySerializer, FundingCategorySerializer, TopicSerializer, TimeSlotSerializer, HostSerializer, UserSerializer from program.utils import tofirstdayinisoweek, get_cached_shows - # Deprecated class CalendarView(TemplateView): template_name = 'calendar.html' @@ -203,6 +202,7 @@ class WeekScheduleView(TemplateView): return context +# Deprecated class StylesView(TemplateView): template_name = 'styles.css' content_type = 'text/css' @@ -255,6 +255,8 @@ def json_playout(request): If end not given, it returns all timeslots of the next 7 days """ + from pv.settings import STATION_FALLBACK_ID + if request.GET.get('start') == None: start = datetime.combine(date.today(), time(0, 0)) else: @@ -295,8 +297,8 @@ def json_playout(request): 'schedule_id': ts.schedule.id, 'is_repetition': ts.is_repetition, 'playlist_id': ts.playlist_id, - 'schedule_fallback_id': ts.schedule.fallback_id, # The schedule's fallback - 'show_fallback_id': ts.show.fallback_id, # The show's fallback + 'schedule_fallback_id': ts.schedule.fallback_id, + 'show_fallback_id': ts.show.fallback_id, 'show_id': ts.show.id, 'show_name': ts.show.name + is_repetition, 'show_hosts': hosts, @@ -306,7 +308,7 @@ def json_playout(request): 'show_musicfocus': musicfocus, 'show_languages': languages, 'show_fundingcategory': fundingcategory.fundingcategory, - 'station_fallback_id': 0, # TODO: The station's global fallback (might change) + 'station_fallback_id': STATION_FALLBACK_ID, # TODO: Find a better way than getting it from the settings 'memo': ts.memo, 'className': classname, } diff --git a/pv/settings.py b/pv/settings.py index ae747e6e..1ad5ba3b 100644 --- a/pv/settings.py +++ b/pv/settings.py @@ -144,6 +144,9 @@ MUSIKPROG_IDS = ( ) SPECIAL_PROGRAM_IDS = () +# The station's fallback playlist ID +STATION_FALLBACK_ID = None + # URL to CBA - Cultural Broadcasting Archive CBA_URL = 'https://cba.fro.at' -- GitLab