forked from rc/aircox
rewrite tests + fix error in schedule generator
This commit is contained in:
parent
d1debc5b9f
commit
502af1dba0
|
@ -414,6 +414,9 @@ class DiffusionPage(Publication):
|
|||
related_name = 'page',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
limit_choices_to = {
|
||||
'initial__isnull': True,
|
||||
},
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -423,7 +426,7 @@ class DiffusionPage(Publication):
|
|||
content_panels = [
|
||||
FieldPanel('diffusion'),
|
||||
] + Publication.content_panels + [
|
||||
InlinePanel('tracks', label=_('Tracks'))
|
||||
InlinePanel('tracks', label=_('Tracks')),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -890,8 +890,8 @@ class SectionLogsList(SectionItem):
|
|||
@register_snippet
|
||||
class SectionTimetable(SectionItem,DatedListBase):
|
||||
class Meta:
|
||||
verbose_name = _('timetable')
|
||||
verbose_name_plural = _('timetable')
|
||||
verbose_name = _('Section: Timetable')
|
||||
verbose_name_plural = _('Sections: Timetable')
|
||||
|
||||
panels = SectionItem.panels + DatedListBase.panels
|
||||
|
||||
|
@ -914,8 +914,8 @@ class SectionTimetable(SectionItem,DatedListBase):
|
|||
@register_snippet
|
||||
class SectionPublicationInfo(SectionItem):
|
||||
class Meta:
|
||||
verbose_name = _('section with publication\'s info')
|
||||
verbose_name = _('sections with publication\'s info')
|
||||
verbose_name = _('Section: publication\'s info')
|
||||
verbose_name_plural = _('Sections: publication\'s info')
|
||||
|
||||
@register_snippet
|
||||
class SectionSearchField(SectionItem):
|
||||
|
@ -933,8 +933,8 @@ class SectionSearchField(SectionItem):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('search field')
|
||||
verbose_name_plural = _('search fields')
|
||||
verbose_name = _('Section: search field')
|
||||
verbose_name_plural = _('Sections: search field')
|
||||
|
||||
panels = SectionItem.panels + [
|
||||
PageChooserPanel('page'),
|
||||
|
@ -946,6 +946,32 @@ class SectionSearchField(SectionItem):
|
|||
context = super().get_context(request, page)
|
||||
list_page = self.page or ListPage.objects.live().first()
|
||||
context['list_page'] = list_page
|
||||
print(context, self.template)
|
||||
return context
|
||||
|
||||
|
||||
@register_snippet
|
||||
class SectionPlayer(SectionItem):
|
||||
live_title = models.CharField(
|
||||
_('live title'),
|
||||
max_length = 32,
|
||||
help_text = _('text to display when it plays live'),
|
||||
)
|
||||
streams = models.TextField(
|
||||
_('audio streams'),
|
||||
help_text = _('one audio stream per line'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Section: Player')
|
||||
|
||||
panels = SectionItem.panels + [
|
||||
FieldPanel('live_title'),
|
||||
FieldPanel('streams'),
|
||||
]
|
||||
|
||||
def get_context(self, request, page):
|
||||
context = super().get_context(request, page)
|
||||
context['streams'] = self.streams.split('\r\n')
|
||||
return context
|
||||
|
||||
|
||||
|
|
280
cms/static/cms/js/player.js
Normal file
280
cms/static/cms/js/player.js
Normal file
|
@ -0,0 +1,280 @@
|
|||
// TODO
|
||||
// - multiple sources for an item
|
||||
// - live streams as item;
|
||||
// - add to playlist button
|
||||
//
|
||||
|
||||
/// Return a human-readable string from seconds
|
||||
function duration_str(seconds) {
|
||||
seconds = Math.floor(seconds);
|
||||
var hours = Math.floor(seconds / 3600);
|
||||
seconds -= hours;
|
||||
var minutes = Math.floor(seconds / 60);
|
||||
seconds -= minutes;
|
||||
|
||||
var str = hours ? (hours < 10 ? '0' + hours : hours) + ':' : '';
|
||||
str += (minutes < 10 ? '0' + minutes : minutes) + ':';
|
||||
str += (seconds < 10 ? '0' + seconds : seconds);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
function Sound(title, detail, stream, duration) {
|
||||
this.title = title;
|
||||
this.detail = detail;
|
||||
this.stream = stream;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
Sound.prototype = {
|
||||
title: '',
|
||||
detail: '',
|
||||
stream: '',
|
||||
duration: undefined,
|
||||
|
||||
item: undefined,
|
||||
|
||||
get seekable() {
|
||||
return this.duration != undefined;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
function PlayerPlaylist(player) {
|
||||
this.player = player;
|
||||
this.playlist = player.player.querySelector('.playlist');
|
||||
this.item_ = player.player.querySelector('.playlist .item');
|
||||
this.items = []
|
||||
}
|
||||
|
||||
PlayerPlaylist.prototype = {
|
||||
items: undefined,
|
||||
|
||||
find: function(stream) {
|
||||
return this.items.find(function(v) {
|
||||
return v.stream == item;
|
||||
});
|
||||
},
|
||||
|
||||
add: function(sound, container) {
|
||||
if(this.find(sound.stream))
|
||||
return;
|
||||
|
||||
var item = this.item_.cloneNode(true);
|
||||
item.removeAttribute('style');
|
||||
|
||||
console.log(sound)
|
||||
item.querySelector('.title').innerHTML = sound.title;
|
||||
if(sound.seekable)
|
||||
item.querySelector('.duration').innerHTML =
|
||||
duration_str(sound.duration);
|
||||
if(sound.detail)
|
||||
item.querySelector('.detail').href = sound.detail;
|
||||
|
||||
item.sound = sound;
|
||||
sound.item = item;
|
||||
|
||||
var self = this;
|
||||
item.querySelector('.action.remove').addEventListener(
|
||||
'click', function(event) { self.remove(sound); }, false
|
||||
);
|
||||
|
||||
(container || this.playlist).appendChild(item);
|
||||
this.items.push(sound);
|
||||
this.save();
|
||||
},
|
||||
|
||||
remove: function(sound) {
|
||||
var index = this.items.indexOf(sound);
|
||||
if(index != -1)
|
||||
this.items.splice(index,1);
|
||||
this.playlist.removeChild(sound.item);
|
||||
this.save();
|
||||
},
|
||||
|
||||
save: function() {
|
||||
var list = [];
|
||||
for(var i in this.items) {
|
||||
var sound = Object.assign({}, this.items[i])
|
||||
delete sound.item;
|
||||
list.push(sound);
|
||||
}
|
||||
this.player.store.set('playlist', list);
|
||||
},
|
||||
|
||||
load: function() {
|
||||
var list = [];
|
||||
var container = document.createDocumentFragment();
|
||||
for(var i in list)
|
||||
this.add(list[i], container)
|
||||
this.playlist.appendChild(container);
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
function Player(id) {
|
||||
this.store = new Store('player');
|
||||
|
||||
// html items
|
||||
this.player = document.getElementById(id);
|
||||
this.box = this.player.querySelector('.box');
|
||||
this.audio = this.player.querySelector('audio');
|
||||
this.controls = {
|
||||
duration: this.box.querySelector('.duration'),
|
||||
progress: this.player.querySelector('progress'),
|
||||
single: this.player.querySelector('input.single'),
|
||||
}
|
||||
|
||||
this.playlist = new PlayerPlaylist(this);
|
||||
this.playlist.load();
|
||||
|
||||
this.init_events();
|
||||
this.load();
|
||||
}
|
||||
|
||||
Player.prototype = {
|
||||
/// current item being played
|
||||
sound: undefined,
|
||||
|
||||
init_events: function() {
|
||||
var self = this;
|
||||
|
||||
function time_from_progress(event) {
|
||||
bounding = self.controls.progress.getBoundingClientRect()
|
||||
offset = (event.clientX - bounding.left);
|
||||
return offset * self.audio.duration / bounding.width;
|
||||
}
|
||||
|
||||
function update_info() {
|
||||
var controls = self.controls;
|
||||
// progress
|
||||
if( !self.sound.seekable ||
|
||||
self.audio.duration == Infinity) {
|
||||
controls.duration.innerHTML = '';
|
||||
controls.progress.value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = self.audio.currentTime;
|
||||
controls.progress.value = pos;
|
||||
controls.progress.max = self.audio.duration;
|
||||
controls.duration.innerHTML = duration_str(sound.duration);
|
||||
}
|
||||
|
||||
// audio
|
||||
this.audio.addEventListener('playing', function() {
|
||||
self.player.setAttribute('state', 'playing');
|
||||
}, false);
|
||||
|
||||
this.audio.addEventListener('pause', function() {
|
||||
self.player.setAttribute('state', 'paused');
|
||||
}, false);
|
||||
|
||||
this.audio.addEventListener('loadstart', function() {
|
||||
self.player.setAttribute('state', 'stalled');
|
||||
}, false);
|
||||
|
||||
this.audio.addEventListener('loadeddata', function() {
|
||||
self.player.removeAttribute('state');
|
||||
}, false);
|
||||
|
||||
this.audio.addEventListener('timeupdate', update_info, false);
|
||||
|
||||
this.audio.addEventListener('ended', function() {
|
||||
if(!self.controls.single.checked)
|
||||
self.next(true);
|
||||
}, false);
|
||||
|
||||
// buttons
|
||||
this.box.querySelector('button.play').onclick = function() {
|
||||
self.play();
|
||||
};
|
||||
|
||||
// progress
|
||||
progress = this.controls.progress;
|
||||
progress.addEventListener('click', function(event) {
|
||||
player.audio.currentTime = time_from_progress(event);
|
||||
}, false);
|
||||
|
||||
progress.addEventListener('mouseout', update_info, false);
|
||||
|
||||
progress.addEventListener('mousemove', function(event) {
|
||||
if(self.audio.duration == Infinity)
|
||||
return;
|
||||
|
||||
var pos = time_from_progress(event);
|
||||
self.controls.duration.innerHTML = duration_str(pos);
|
||||
}, false);
|
||||
},
|
||||
|
||||
play: function() {
|
||||
if(this.audio.paused)
|
||||
this.audio.play();
|
||||
else
|
||||
this.audio.pause();
|
||||
},
|
||||
|
||||
unselect: function(sound) {
|
||||
sound.item.removeAttribute('selected');
|
||||
},
|
||||
|
||||
select: function(sound, play = true) {
|
||||
if(this.sound)
|
||||
this.unselect(this.sound);
|
||||
|
||||
this.audio.pause();
|
||||
|
||||
// if stream is a list, use <source>
|
||||
if(sound.stream.splice) {
|
||||
this.audio.src="";
|
||||
|
||||
var sources = this.audio.querySelectorAll('source');
|
||||
for(var i in sources)
|
||||
this.audio.removeChild(sources[i]);
|
||||
|
||||
for(var i in sound.stream) {
|
||||
var source = document.createElement('source');
|
||||
source.src = sound.stream[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
this.audio.src = sound.stream;
|
||||
this.audio.load();
|
||||
|
||||
this.sound = sound;
|
||||
sound.item.setAttribute('selected', 'true');
|
||||
|
||||
this.box.querySelector('.title').innerHTML = sound.title;
|
||||
if(play)
|
||||
this.play();
|
||||
},
|
||||
|
||||
next: function() {
|
||||
var index = this.playlist.items.indexOf(this.sound);
|
||||
if(index < 0)
|
||||
return;
|
||||
|
||||
index++;
|
||||
if(index < this.playlist.items.length)
|
||||
this.select(this.playlist.items[index], true);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.store.set('player', {
|
||||
single: this.controls.single.checked,
|
||||
sound: this.sound && this.sound.stream,
|
||||
});
|
||||
},
|
||||
|
||||
load: function() {
|
||||
var data = this.store.get('player');
|
||||
this.controls.single.checked = data.single;
|
||||
this.sound = this.playlist.find(data.stream);
|
||||
},
|
||||
|
||||
update_on_air: function() {
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
|
68
cms/static/cms/js/utils.js
Normal file
68
cms/static/cms/js/utils.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
|
||||
/// Helper to provide a tab+panel functionnality; the tab and the selected
|
||||
/// element will have an attribute "selected".
|
||||
/// We assume a common ancestor between tab and panel at a maximum level
|
||||
/// of 2.
|
||||
/// * tab: corresponding tab
|
||||
/// * panel_selector is used to select the right panel object.
|
||||
function select_tab(tab, panel_selector) {
|
||||
var parent = tab.parentNode.parentNode;
|
||||
var panel = parent.querySelector(panel_selector);
|
||||
|
||||
// unselect
|
||||
var qs = parent.querySelectorAll('*[selected]');
|
||||
for(var i = 0; i < qs.length; i++)
|
||||
if(qs[i] != tab && qs[i] != panel)
|
||||
qs[i].removeAttribute('selected');
|
||||
|
||||
panel.setAttribute('selected', 'true');
|
||||
tab.setAttribute('selected', 'true');
|
||||
}
|
||||
|
||||
|
||||
/// Utility to store objects in local storage. Data are stringified in JSON
|
||||
/// format in order to keep type.
|
||||
function Store(prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
Store.prototype = {
|
||||
// save data to localstorage, or remove it if data is null
|
||||
set: function(key, data) {
|
||||
key = this.prefix + '.' + key;
|
||||
if(data == undefined) {
|
||||
localStorage.removeItem(prefix);
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
},
|
||||
|
||||
// load data from localstorage
|
||||
get: function(key) {
|
||||
try {
|
||||
key = this.prefix + '.' + key;
|
||||
var data = localStorage.getItem(key);
|
||||
if(data)
|
||||
return JSON.parse(data);
|
||||
}
|
||||
catch(e) { console.log(e, data); }
|
||||
},
|
||||
|
||||
// return true if the given item is stored
|
||||
exists: function(key) {
|
||||
key = this.prefix + '.' + key;
|
||||
return (localStorage.getItem(key) != null);
|
||||
},
|
||||
|
||||
// update a field in the stored data
|
||||
update: function(key, field_key, value) {
|
||||
data = this.get(key) || {};
|
||||
if(value)
|
||||
data[field_key] = value;
|
||||
else
|
||||
delete data[field_key];
|
||||
this.set(key, data);
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -25,6 +25,9 @@
|
|||
{% block css_extras %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
<script src="{% static 'cms/js/utils.js' %}"></script>
|
||||
<script src="{% static 'cms/js/player.js' %}"></script>
|
||||
|
||||
<title>{{ page.title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
61
cms/templates/cms/sections/section_player.html
Normal file
61
cms/templates/cms/sections/section_player.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% extends 'cms/sections/section_item.html' %}
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<div id="player">
|
||||
<div class="box">
|
||||
<audio preload="metadata">
|
||||
{% trans "Your browser does not support the <code>audio</code> element." %}
|
||||
{% for stream in streams %}
|
||||
<source src="{{ stream }}" />
|
||||
{% endfor %}
|
||||
</audio>
|
||||
|
||||
<button class="play" onclick="Player.play()"
|
||||
title="{% trans "play/pause" %}"></button>
|
||||
|
||||
<h3 class="title">{{ self.live_title }}</h3>
|
||||
|
||||
<div>
|
||||
<div class="info duration"></div>
|
||||
<progress value="0" max="1"></progress>
|
||||
|
||||
<input type="checkbox" class="single" id="player_single_mode">
|
||||
<label for="player_single_mode"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="playlist">
|
||||
<li class='item' style="display: none;">
|
||||
<h2 class="title">{{ self.live_title }}</h2>
|
||||
<div class="info duration"></div>
|
||||
<div class="actions">
|
||||
<a class="action detail" title="{% trans "more informations" %}">➔</a>
|
||||
<a class="action remove" title="{% trans "remove this sound" %}">✖</a>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
<div class='item on_air'>
|
||||
<h2 class="title"></h2>
|
||||
<a class="url">➔</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var player = new Player('player');
|
||||
player.playlist.add(new Sound('{{ self.live_title }}', '', [
|
||||
{% for stream in streams %}'{{ stream }}',{% endfor %}
|
||||
]));
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
@ -1,27 +1,5 @@
|
|||
{% load i18n %}
|
||||
|
||||
<script>
|
||||
/// Function used to select a panel on a tab selection.
|
||||
/// The tab should be at max level -2 of the main container
|
||||
/// The panel must have a class "panel"
|
||||
function select_tab(target) {
|
||||
parent = target.parentNode.parentNode;
|
||||
|
||||
var date = target.dataset.date;
|
||||
panel = parent.querySelector('.panel[data-date="' + date + '"]');
|
||||
|
||||
// unselect
|
||||
qs = parent.querySelectorAll('*[selected]');
|
||||
for(var i = 0; i < qs.length; i++)
|
||||
if(qs[i].dataset.date != date)
|
||||
qs[i].removeAttribute('selected');
|
||||
|
||||
console.log(panel, target, date);
|
||||
panel.setAttribute('selected', 'true');
|
||||
target.setAttribute('selected', 'true');
|
||||
}
|
||||
</script>
|
||||
|
||||
{# FIXME: get current complete URL #}
|
||||
<div class="list date_list">
|
||||
{% if nav_dates %}
|
||||
|
@ -31,7 +9,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% for day in nav_dates.dates %}
|
||||
<a onclick="select_tab(this);" data-date="day_{{day|date:"Y-m-d"}}"
|
||||
<a onclick="select_tab(this, '.panel[data-date=\'{{day|date:"Y-m-d"}}\']');"
|
||||
{% if day == nav_dates.date %}selected{% endif %}
|
||||
class="tab {% if day == nav_dates.date %}today{% endif %}">
|
||||
{{ day|date:'D. d' }}
|
||||
|
@ -47,7 +25,7 @@
|
|||
{% for day, list in object_list %}
|
||||
<ul class="panel {% if day == nav_dates.date %}class="today"{% endif %}"
|
||||
{% if day == nav_dates.date %}selected{% endif %}
|
||||
data-date="day_{{day|date:"Y-m-d"}}">
|
||||
data-date="{{day|date:"Y-m-d"}}">
|
||||
{# you might like to hide it by default -- this more for sections #}
|
||||
<h2>{{ day|date:'l d F' }}</h2>
|
||||
{% with object_list=list item_date_format="H:i" %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import calendar
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
|
@ -400,6 +401,18 @@ class Schedule(models.Model):
|
|||
date = date_or_default(date, True).replace(day=1)
|
||||
freq = self.frequency
|
||||
|
||||
# last of the month
|
||||
if freq == Schedule.Frequency.last:
|
||||
date = date.replace(day=calendar.monthrange(date.year, date.month)[1])
|
||||
|
||||
# end of month before the wanted weekday: move one week back
|
||||
if date.weekday() < self.date.weekday():
|
||||
date -= datetime.timedelta(days = 7)
|
||||
|
||||
delta = self.date.weekday() - date.weekday()
|
||||
date += datetime.timedelta(days = delta)
|
||||
return [self.normalize(date)]
|
||||
|
||||
# move to the first day of the month that matches the schedule's weekday
|
||||
# check on SO#3284452 for the formula
|
||||
first_weekday = date.weekday()
|
||||
|
@ -408,19 +421,9 @@ class Schedule(models.Model):
|
|||
- first_weekday + sched_weekday)
|
||||
month = date.month
|
||||
|
||||
# last of the month
|
||||
if freq == Schedule.Frequency.last:
|
||||
date += tz.timedelta(days = 4 * 7)
|
||||
next_date = date + tz.timedelta(days = 7)
|
||||
if next_date.month == month:
|
||||
date = next_date
|
||||
return [self.normalize(date)]
|
||||
|
||||
dates = []
|
||||
if freq == Schedule.Frequency.one_on_two:
|
||||
# NOTE previous algorithm was based on the week number, but this
|
||||
# approach is wrong because number of weeks in a year can be
|
||||
# 52 or 53. This also clashes with the first week of the year.
|
||||
# check date base on a diff of dates base on a 14 days delta
|
||||
diff = as_date(date, False) - as_date(self.date, False)
|
||||
if diff.days % 14:
|
||||
date += tz.timedelta(days = 7)
|
||||
|
@ -445,36 +448,31 @@ class Schedule(models.Model):
|
|||
If exclude_saved, exclude all diffusions that are yet in the database.
|
||||
"""
|
||||
dates = self.dates_of_month(date)
|
||||
saved = Diffusion.objects.filter(start__in = dates,
|
||||
program = self.program)
|
||||
diffusions = []
|
||||
|
||||
duration = utils.to_timedelta(self.duration)
|
||||
|
||||
# existing diffusions
|
||||
for item in saved:
|
||||
for item in Diffusion.objects.filter(
|
||||
program = self.program, start__in = dates):
|
||||
if item.start in dates:
|
||||
dates.remove(item.start)
|
||||
if not exclude_saved:
|
||||
diffusions.append(item)
|
||||
|
||||
# others
|
||||
for date in dates:
|
||||
first_date = date
|
||||
if self.initial:
|
||||
first_date -= self.date - self.initial.date
|
||||
|
||||
first_diffusion = Diffusion.objects.filter(start = first_date,
|
||||
program = self.program)
|
||||
first_diffusion = first_diffusion[0] if first_diffusion.count() \
|
||||
else None
|
||||
diffusions.append(Diffusion(
|
||||
program = self.program,
|
||||
type = Diffusion.Type.unconfirmed,
|
||||
initial = first_diffusion if self.initial else None,
|
||||
start = date,
|
||||
end = date + duration,
|
||||
))
|
||||
# new diffusions
|
||||
duration = utils.to_timedelta(self.duration)
|
||||
if self.initial:
|
||||
delta = self.date - self.initial.date
|
||||
diffusions += [
|
||||
Diffusion(
|
||||
program = self.program,
|
||||
type = Diffusion.Type.unconfirmed,
|
||||
initial = \
|
||||
Diffusion.objects.filter(start = date - delta).first() \
|
||||
if self.initial else None,
|
||||
start = date,
|
||||
end = date + duration,
|
||||
) for date in dates
|
||||
]
|
||||
return diffusions
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -1,79 +1,66 @@
|
|||
import datetime
|
||||
import calendar
|
||||
import logging
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from aircox.programs.models import *
|
||||
|
||||
logger = logging.getLogger('aircox.test')
|
||||
logger.setLevel('INFO')
|
||||
|
||||
class Programs (TestCase):
|
||||
def setUp (self):
|
||||
stream = Stream.objects.get_or_create(
|
||||
name = 'diffusions',
|
||||
defaults = { 'type': Stream.Type.schedule }
|
||||
)[0]
|
||||
Program.objects.create(name = 'source', stream = stream)
|
||||
Program.objects.create(name = 'microouvert', stream = stream)
|
||||
class ScheduleCheck (TestCase):
|
||||
def setUp(self):
|
||||
self.schedules = [
|
||||
Schedule(
|
||||
date = tz.now(),
|
||||
duration = datetime.time(1,30),
|
||||
frequency = frequency,
|
||||
)
|
||||
for frequency in Schedule.Frequency.__members__.values()
|
||||
]
|
||||
|
||||
self.schedules = {}
|
||||
self.programs = {}
|
||||
def test_frequencies(self):
|
||||
for schedule in self.schedules:
|
||||
logger.info('- test frequency %s' % schedule.get_frequency_display())
|
||||
date = schedule.date
|
||||
count = 24
|
||||
while count:
|
||||
logger.info('- month %(month)s/%(year)s' % {
|
||||
'month': date.month,
|
||||
'year': date.year
|
||||
})
|
||||
count -= 1
|
||||
dates = schedule.dates_of_month(date)
|
||||
if schedule.frequency == schedule.Frequency.one_on_two:
|
||||
self.check_one_on_two(schedule, date, dates)
|
||||
elif schedule.frequency == schedule.Frequency.last:
|
||||
self.check_last(schedule, date, dates)
|
||||
else:
|
||||
pass
|
||||
date += relativedelta(months = 1)
|
||||
|
||||
def test_create_programs_schedules (self):
|
||||
program = Program.objects.get(name = 'source')
|
||||
def check_one_on_two(self, schedule, date, dates):
|
||||
for date in dates:
|
||||
delta = date.date() - schedule.date.date()
|
||||
self.assertEqual(delta.days % 14, 0)
|
||||
|
||||
sched_0 = self.create_schedule(program, 'one on two', [
|
||||
tz.datetime(2015, 10, 2, 18),
|
||||
tz.datetime(2015, 10, 16, 18),
|
||||
tz.datetime(2015, 10, 30, 18),
|
||||
]
|
||||
)
|
||||
sched_1 = self.create_schedule(program, 'one on two', [
|
||||
tz.datetime(2015, 10, 5, 18),
|
||||
tz.datetime(2015, 10, 19, 18),
|
||||
],
|
||||
rerun = sched_0
|
||||
)
|
||||
def check_last(self, schedule, date, dates):
|
||||
month_info = calendar.monthrange(date.year, date.month)
|
||||
date = datetime.date(date.year, date.month, month_info[1])
|
||||
|
||||
self.programs[program.pk] = program
|
||||
# end of month before the wanted weekday: move one week back
|
||||
if date.weekday() < schedule.date.weekday():
|
||||
date -= datetime.timedelta(days = 7)
|
||||
|
||||
program = Program.objects.get(name = 'microouvert')
|
||||
# special case with november first week starting on sunday
|
||||
sched_2 = self.create_schedule(program, 'first and third', [
|
||||
tz.datetime(2015, 11, 6, 18),
|
||||
tz.datetime(2015, 11, 20, 18),
|
||||
],
|
||||
date = tz.datetime(2015, 10, 23, 18),
|
||||
)
|
||||
date -= datetime.timedelta(days = date.weekday())
|
||||
date += datetime.timedelta(days = schedule.date.weekday())
|
||||
self.assertEqual(date, dates[0].date())
|
||||
|
||||
def check_n_of_week(self, schedule, date, dates):
|
||||
pass
|
||||
|
||||
def create_schedule (self, program, frequency, dates, date = None, rerun = None):
|
||||
frequency = Schedule.Frequency[frequency]
|
||||
schedule = Schedule(
|
||||
program = program,
|
||||
frequency = frequency,
|
||||
date = date or dates[0],
|
||||
rerun = rerun,
|
||||
duration = datetime.time(1, 30)
|
||||
)
|
||||
print(schedule.__dict__)
|
||||
schedule.save()
|
||||
|
||||
self.schedules[schedule.pk] = (schedule, dates)
|
||||
return schedule
|
||||
|
||||
def test_check_schedule (self):
|
||||
for schedule, dates in self.schedules:
|
||||
dates = [ tz.make_aware(date) for date in dates ]
|
||||
dates.sort()
|
||||
|
||||
# dates
|
||||
dates_ = schedule.dates_of_month(dates[0])
|
||||
dates_.sort()
|
||||
self.assertEqual(dates_, dates)
|
||||
|
||||
# diffusions
|
||||
dates_ = schedule.diffusions_of_month(dates[0])
|
||||
dates_ = [date_.date for date_ in dates_]
|
||||
dates_.sort()
|
||||
self.assertEqual(dates_, dates)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user