Skip to content
Snippets Groups Projects
Commit 822d5e0f authored by Ingo Leindecker's avatar Ingo Leindecker
Browse files

Properly extend schedule and readme update

...as well as some minor changes and bug fixes.

See #8 #10 #22 #24
parent bdb6c51b
No related branches found
No related tags found
No related merge requests found
=================================
Radio Helsinki Program Management
=================================
================================
AURA Steering: Program Scheduler
================================
Installation
============
To get setup you must have the following installed:
* MySQL-Client Development libraries
* MySQL-Client Development libraries
* JPEG library development files
* Python 2.7 including Development files
* Python 3.5 including Development files
* virtualenv 1.11
In Debian or Ubuntu (or derivatives) you should be able to achieve this with this command:
$ sudo apt-get install libmysqlclient-dev libjpeg-dev python2.7-dev virtualenv
$ sudo apt-get install libmysqlclient-dev libjpeg-dev python3.5-dev virtualenv
Setting up the environment
--------------------------
Create a virtual environment where the dependencies will live::
Create a virtual environment where the dependencies will live:
$ virtualenv -p python2.7 python
$ virtualenv -p python3.5 python
$ source python/bin/activate
(python)$
Change into the base directory of this software and install the project dependencies::
Change into the base directory of this software and install the project dependencies:
(python)$ pip install -r requirements.txt
(python)$ pip3 install -r requirements.txt
Setting up the database
-----------------------
By default the project is set up to run on a SQLite 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 line
Create a file pv/local_settings.py and add at least the line
SECRET_KEY = 'secret key'
(obviously replacing "secret key" with a key of you choice).
(obviously replacing "secret key" with a key of your choice).
Then run::
Then run:
(python)$ python manage.py migrate
(python)$ python manage.py loaddata program/fixtures/*.yaml
### Setting up MySQL
__Note:__ When adding your database, make sure you _don't_ use the collation _utf8mb4_unicode_ci_ or you will get a key length error during migration. (use e.g. _utf8_general_ci_ instead).
To use MySQL, add the following to your local_settings.py (before migrating):
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'OPTIONS': {
'read_default_file': os.path.join(PROJECT_DIR, 'mysql.cnf'),
},
}
}
Create a file pv/mysql.cnf and give your MySQL credentials:
[client]
database =
host = localhost
port = 3309
user =
password =
default-character-set = utf8
Adding an admin user
--------------------
In order to create an admin user (which you will need to login to the webinterface after the next step) run::
In order to create an admin user (which you will need to login to the webinterface after the next step) run:
(python)$ python manage.py createsuperuser
......@@ -59,9 +84,8 @@ In order to create an admin user (which you will need to login to the webinterfa
Running a web server
--------------------
In development you should run::
In development you should run:
(python)$ python manage.py runserver
After this you can open http://127.0.0.1:8000/admin in your browser and log in with the admin credential you created previously.
After this you can open http://127.0.0.1:8000/admin in your browser and log in with the admin credential you created previously.
\ No newline at end of file
......@@ -102,8 +102,8 @@ def json_frapp(request):
metainfos.append({ 'key': 'ProduzentIn', 'value': ', '.join(ts.show.hosts.values_list('name', flat=True)) })
metainfos.append({ 'key': 'E-Mail', 'value': ', '.join(ts.show.hosts.values_list('email', flat=True)) })
image = '' if s.image.name == None else str(get_current_site(request)) + MEDIA_URL + s.image.name
url = '' if s.website == None else s.website
image = '' if s.image.name == None or s.image.name == '' else str(get_current_site(request)) + MEDIA_URL + s.image.name
url = '' if s.website == None or s.website == '' else s.website
# Get active schedules for the given date
# But include upcoming single timeslots (with rrule_id=1)
......
......@@ -7,7 +7,6 @@ from django.forms.models import model_to_dict
from django.utils.translation import ugettext_lazy as _
from versatileimagefield.fields import VersatileImageField, PPOIField
from django.conf import settings
import hashlib
from tinymce import models as tinymce_models
......@@ -432,7 +431,7 @@ class Schedule(models.Model):
def instantiate_upcoming(sdl, show_pk, pk=None):
"""Returns an upcoming schedule instance for conflict resolution"""
pk = int(show_pk) if pk != None else None
pk = int(pk) if pk != None else None
rrule = RRule.objects.get(pk=int(sdl['rrule']))
show = Show.objects.get(pk=int(show_pk))
......@@ -441,7 +440,9 @@ class Schedule(models.Model):
automation_id = int(sdl['automation_id']) if sdl['automation_id'] else None
dstart = datetime.strptime(str(sdl['dstart']), '%Y-%m-%d').date()
if dstart < datetime.today().date(): # Schedule mustn't start in the past
# Schedule mustn't start in the past when adding
if pk == None and dstart < datetime.today().date():
dstart = datetime.today().date()
tstart = sdl['tstart'] + ':00' if len(str(sdl['tstart'])) == 5 else sdl['tstart']
......@@ -731,11 +732,23 @@ class Schedule(models.Model):
Returns conflicts dict
"""
# Generate schedule
# Generate schedule to be saved
schedule = Schedule.instantiate_upcoming(sdl, show_pk, schedule_pk)
# Copy if dstart changes for generating timeslots
gen_schedule = schedule
# Generate timeslots
timeslots = Schedule.generate_timeslots(schedule)
# If extending: Get last timeslot and start generating from that date on
if schedule_pk != None:
existing_schedule = Schedule.objects.get(pk=int(schedule_pk))
if schedule.until > existing_schedule.until:
last_timeslot = TimeSlot.objects.filter(schedule=existing_schedule).order_by('start').reverse()[0]
gen_schedule.dstart = last_timeslot.start.date() + timedelta(days=1)
timeslots = Schedule.generate_timeslots(gen_schedule)
# Generate conflicts and add schedule
conflicts = Schedule.generate_conflicts(timeslots)
......@@ -759,7 +772,7 @@ class Schedule(models.Model):
# Regenerate conflicts
schedule = Schedule.instantiate_upcoming(sdl, show_pk, schedule_pk)
show = schedule.show
conflicts = Schedule.make_conflicts(sdl, schedule.pk, show.pk)
conflicts = Schedule.make_conflicts(sdl, schedule_pk, show_pk)
if schedule.rrule.freq > 0 and schedule.dstart == schedule.until:
return {'detail': _("Start and until dates mustn't be the same")}
......@@ -767,7 +780,7 @@ class Schedule(models.Model):
if schedule.until < schedule.dstart:
return {'detail': _("Until date mustn't before start")}
num_conflicts = len([pr for pr in conflicts['projected'] if len(x['collisions']) > 0])
num_conflicts = len([pr for pr in conflicts['projected'] if len(pr['collisions']) > 0])
if len(solutions) != num_conflicts:
return {'detail': _("Numbers of conflicts and solutions don't match.")}
......@@ -976,7 +989,7 @@ class Schedule(models.Model):
# Only save schedule if timeslots were created
if create:
# Create or save schedule
# Create or update schedule
schedule.save()
# Delete upcoming timeslots which still remain
......@@ -1122,7 +1135,7 @@ class TimeSlot(models.Model):
self.show = self.schedule.show
# Generate a distinct and reproducible hash for the timeslot
# Makes sure none of these fields changed when updating
# Makes sure none of these fields changed when updating a schedule
string = str(self.start) + str(self.end) + str(self.schedule.rrule.id) + str(self.schedule.byweekday)
self.hash = str(''.join(s for s in string if s.isdigit()))
return self
......
......@@ -110,6 +110,8 @@
<div id="show-type"></div>
<div id="show-musicfocus"></div>
<div id="show-rtrcategory"></div>
<p></p>
<div id="memo"></div>
<div id="response-message"></div>
</div>
</div>
......@@ -225,7 +227,7 @@
jQuery("#show-categories").html('Categories: ' + calEvent.show_categories);
jQuery("#show-topics").html('Topics: ' + calEvent.show_topics);
jQuery("#show-musicfocus").html('Music focus: ' + calEvent.show_musicfocus);
jQuery("#is-repetition").html('WH: ' + calEvent.is_repetition);
jQuery("#is-repetition").html('Is repetition: ' + calEvent.is_repetition);
jQuery("#playlist-id").html('Playlist ID: ' + calEvent.playlist_id);
jQuery("#fallback-id").html('Fallback ID: ' + calEvent.fallback_id);
jQuery("#memo").html(calEvent.memo);
......
......@@ -599,9 +599,7 @@ class APIScheduleViewSet(viewsets.ModelViewSet):
Create a schedule, generate timeslots, test for collisions and resolve them including notes
Only superusers may add schedules
TODO: if nothing changed except for is_repetition, fallback_id or automation_id
TODO: Prolonging a schedule properly withouth matching against itself
+ Perhaps directly insert into database if no conflicts found
TODO: Perhaps directly insert into database if no conflicts found
"""
# Only allow creating when calling /shows/1/schedules/
......@@ -646,6 +644,7 @@ class APIScheduleViewSet(viewsets.ModelViewSet):
# First update submit -> return projected timeslots and collisions
if not 'solutions' in request.data:
# TODO: If nothing else than fallback_id, automation_id or is_repetition changed -> just save and don't do anything
return Response(Schedule.make_conflicts(request.data['schedule'], pk, show_pk))
# Otherwise try to resolve
......
......@@ -52,8 +52,6 @@ django.jQuery(document).ready( function() {
var ps_id = django.jQuery(this).closest('tr').attr("id");
var dstart = django.jQuery(this).val();
//django.jQuery(this).removeClass('validation-error');
/* Set the until date to dstart if editing a programslot with freq 'once' */
if( django.jQuery('#id_' + ps_id + '-rrule option:selected').val() == 1 ) {
django.jQuery('#id_' + ps_id + '-until').show().val(dstart).hide();
......@@ -92,11 +90,6 @@ django.jQuery(document).ready( function() {
alert('Enddatum darf nicht vor dem Startdatum liegen');
django.jQuery(this).addClass('validation-error');
}
} else if( until == '' && dstart != '') {
e.preventDefault();
alert('Enddatum darf nicht leer sein');
django.jQuery(this).addClass('validation-error');
} else if( dstart == '' && until != '') {
e.preventDefault();
alert('Startdatum darf nicht leer sein');
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment