diff --git a/aircox/admin.py b/aircox/admin.py
index 2af1bee..eb807a9 100755
--- a/aircox/admin.py
+++ b/aircox/admin.py
@@ -101,9 +101,9 @@ class DiffusionAdmin(admin.ModelAdmin):
sounds = [ str(s) for s in obj.get_archives()]
return ', '.join(sounds) if sounds else ''
- def conflicts(self, obj):
- if obj.type == Diffusion.Type.unconfirmed:
- return ', '.join([ str(d) for d in obj.get_conflicts()])
+ def conflicts_(self, obj):
+ if obj.conflicts.count():
+ return obj.conflicts.count()
return ''
def end_time(self, obj):
@@ -113,12 +113,13 @@ class DiffusionAdmin(admin.ModelAdmin):
def first(self, obj):
return obj.initial.start if obj.initial else ''
- list_display = ('id', 'program', 'start', 'end_time', 'type', 'first', 'archives', 'conflicts')
+ list_display = ('id', 'program', 'start', 'end_time', 'type', 'first', 'archives', 'conflicts_')
list_filter = ('type', 'start', 'program')
list_editable = ('type',)
ordering = ('-start', 'id')
- fields = ['type', 'start', 'end', 'initial', 'program']
+ fields = ['type', 'start', 'end', 'initial', 'program', 'conflicts']
+ readonly_fields = ('conflicts',)
inlines = [ DiffusionInline, SoundInline ]
diff --git a/aircox/management/commands/diffusions.py b/aircox/management/commands/diffusions.py
index 9841411..8e4b28b 100755
--- a/aircox/management/commands/diffusions.py
+++ b/aircox/management/commands/diffusions.py
@@ -25,43 +25,12 @@ from aircox.models import *
logger = logging.getLogger('aircox.tools')
+import time
+
class Actions:
- @staticmethod
- def __check_conflicts (item, saved_items):
- """
- Check for conflicts, and update conflictual
- items if they have been generated during this
- update.
-
- It set an attribute 'do_not_save' if the item should not
- be saved. FIXME: find proper way
-
- Return the number of conflicts
- """
- conflicts = list(item.get_conflicts())
- for i, conflict in enumerate(conflicts):
- if conflict.program == item.program:
- item.do_not_save = True
- del conflicts[i]
- continue
-
- if conflict.pk in saved_items and \
- conflict.type != Diffusion.Type.unconfirmed:
- conflict.type = Diffusion.Type.unconfirmed
- conflict.save()
-
- if not conflicts:
- item.type = Diffusion.Type.normal
- return 0
-
- item.type = Diffusion.Type.unconfirmed
- return len(conflicts)
-
@classmethod
def update (cl, date, mode):
manual = (mode == 'manual')
- if not manual:
- saved_items = set()
count = [0, 0]
for schedule in Schedule.objects.filter(program__active = True) \
@@ -71,16 +40,15 @@ class Actions:
items = schedule.diffusions_of_month(date, exclude_saved = True)
count[0] += len(items)
- if manual:
- Diffusion.objects.bulk_create(items)
- else:
- for item in items:
- count[1] += cl.__check_conflicts(item, saved_items)
- if hasattr(item, 'do_not_save'):
- count[0] -= 1
- continue
- item.save()
- saved_items.add(item)
+ # we can't bulk create because we ned signal processing
+ for item in items:
+ conflicts = item.get_conflicts()
+ item.type = Diffusion.Type.unconfirmed \
+ if manual or conflicts.count() else \
+ Diffusion.Type.normal
+ item.save(no_check = True)
+ if conflicts.count():
+ item.conflicts.set(conflicts.all())
logger.info('[update] schedule %s: %d new diffusions',
str(schedule), len(items),
diff --git a/aircox/models.py b/aircox/models.py
index 8105994..22fcf22 100755
--- a/aircox/models.py
+++ b/aircox/models.py
@@ -720,19 +720,6 @@ class DiffusionManager(models.Manager):
qs = self if qs is None else qs
return qs.filter(program__station = station)
- @staticmethod
- def __in_range(field, range, field_ = None, reversed = False):
- """
- Return a kwargs to catch diffusions based on the given field name
- and datetime range.
- """
- if reversed:
- return { field + "__lte": range[1],
- (field_ or field) + "__gte": range[0] }
-
- return { field + "__gte" : range[0],
- (field_ or field) + "__lte" : range[1] }
-
def at(self, station, date = None, next = False, qs = None):
"""
Return diffusions occuring at the given date, ordered by +start
@@ -754,22 +741,21 @@ class DiffusionManager(models.Manager):
if isinstance(date, datetime.datetime):
# use datetime: we want diffusion that occurs around this
# range
- range = date, date
- filters = self.__in_range('start', range, 'end', True)
+ filters = { 'start__lte': date, 'end__gte': date }
if next:
qs = qs.filter(
- models.Q(date__gte = date) | models.Q(**filters)
+ models.Q(start__gte = date) | models.Q(**filters)
)
else:
qs = qs.filter(**filters)
else:
# use date: we want diffusions that occurs this day
- range = utils.date_range(date)
- filters = models.Q(**self.__in_range('start', range)) | \
- models.Q(**self.__in_range('end', range))
+ start, end = utils.date_range(date)
+ filters = models.Q(start__gte = start, start__lte = end) | \
+ models.Q(end__gt = start, end__lt = end)
if next:
# include also diffusions of the next day
- filters |= models.Q(start__gte = range[0])
+ filters |= models.Q(start__gte = start)
qs = qs.filter(filters)
return self.station(station, qs).order_by('start').distinct()
@@ -842,6 +828,12 @@ class Diffusion(models.Model):
# blank = True, null = True,
# help_text = _('use this input port'),
# )
+ conflicts = models.ManyToManyField(
+ 'self',
+ verbose_name = _('conflicts'),
+ blank = True,
+ help_text = _('conflicts'),
+ )
start = models.DateTimeField( _('start of the diffusion') )
end = models.DateTimeField( _('end of the diffusion') )
@@ -891,22 +883,44 @@ class Diffusion(models.Model):
"""
Return a list of conflictual diffusions, based on the scheduled duration.
"""
- r = Diffusion.objects.filter(
+ return Diffusion.objects.filter(
models.Q(start__lt = self.start,
end__gt = self.start) |
models.Q(start__gt = self.start,
start__lt = self.end)
)
- return r
- def save(self, *args, **kwargs):
+ def check_conflicts(self):
+ conflicts = self.get_conflicts()
+ self.conflicts.set(conflicts)
+
+ __initial = None
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.__initial = {
+ 'start': self.start,
+ 'end': self.end,
+ }
+
+ def save(self, no_check = False, *args, **kwargs):
+ if no_check:
+ return super().save(*args, **kwargs)
+
if self.initial:
# force link to the top initial diffusion
if self.initial.initial:
self.initial = self.initial.initial
self.program = self.initial.program
+
super().save(*args, **kwargs)
+ if self.__initial:
+ if self.start != self.__initial['start'] or \
+ self.end != self.__initial['end']:
+ self.check_conflicts()
+
+
def __str__(self):
return '{self.program.name} {date} #{self.pk}'.format(
self=self, date=self.date.strftime('%Y-%m-%d %H:%M')
diff --git a/aircox_cms/admin.py b/aircox_cms/admin.py
index 8c38f3f..3c21844 100755
--- a/aircox_cms/admin.py
+++ b/aircox_cms/admin.py
@@ -1,3 +1,6 @@
from django.contrib import admin
# Register your models here.
+
+
+
diff --git a/aircox_cms/static/wagtailadmin/css/core.css b/aircox_cms/static/wagtailadmin/css/core.css
index 3d5ec48..47d7d32 100644
--- a/aircox_cms/static/wagtailadmin/css/core.css
+++ b/aircox_cms/static/wagtailadmin/css/core.css
@@ -150,7 +150,7 @@
@-webkit-keyframes MenuAnimOut1 {
50% {
- -webkit-transform: translateZ(-250px) rotateY(30deg);
+ -webkit-transform: translateZ(-220px) rotateY(30deg);
}
75% {
-webkit-transform: translateZ(-372.5px) rotateY(15deg);
@@ -192,8 +192,8 @@
@keyframes MenuAnimOut1 {
50% {
- -webkit-transform: translateZ(-250px) rotateY(30deg);
- transform: translateZ(-250px) rotateY(30deg);
+ -webkit-transform: translateZ(-220px) rotateY(30deg);
+ transform: translateZ(-220px) rotateY(30deg);
}
75% {
-webkit-transform: translateZ(-372.5px) rotateY(15deg);
@@ -270,7 +270,7 @@
opacity: 0;
}
20% {
- -webkit-transform: translateZ(-250px) rotateY(30deg);
+ -webkit-transform: translateZ(-220px) rotateY(30deg);
opacity: 0.5;
}
100% {
@@ -330,8 +330,8 @@
opacity: 0;
}
20% {
- -webkit-transform: translateZ(-250px) rotateY(30deg);
- transform: translateZ(-250px) rotateY(30deg);
+ -webkit-transform: translateZ(-220px) rotateY(30deg);
+ transform: translateZ(-220px) rotateY(30deg);
opacity: 0.5;
}
100% {
@@ -1932,7 +1932,7 @@ body.ready .fade {
@media screen and (min-width: 50em) {
.modal-dialog {
- padding: 0 0 2em 180px;
+ padding: 0 0 2em 220px;
}
}
@@ -3846,7 +3846,7 @@ table.listing {
}
.listing.images > li .image {
text-align: center;
- height: 180px;
+ height: 220px;
}
.listing.images > li .image:before {
content: '';
@@ -4881,8 +4881,8 @@ body.ready .progress .bar {
.nav-wrapper {
position: relative;
- margin-left: -180px;
- width: 180px;
+ margin-left: -220px;
+ width: 220px;
float: left;
height: 100%;
min-height: 800px;
@@ -5005,13 +5005,13 @@ body.ready .nav-main a {
display: none;
}
-.nav-submenu .menu-item a {
- white-space: normal;
- padding: 0.9em 1.7em 0.9em 4.5em;
+.nav-submenu .menu-item .info {
+ margin-right: 1em;
}
-.nav-submenu .menu-item a:before {
- margin-left: -1.5em;
+.nav-submenu .menu-item a {
+ white-space: normal;
+ padding: 0.9em 1em 0.9em 1em;
}
.nav-submenu .menu-item a:hover {
@@ -5106,8 +5106,8 @@ body.ready .nav-main a {
}
body.nav-open .wrapper {
- transform: translate3d(180px, 0, 0);
- -webkit-transform: translate3d(180px, 0, 0);
+ transform: translate3d(220px, 0, 0);
+ -webkit-transform: translate3d(220px, 0, 0);
}
body.nav-open .content-wrapper {
@@ -5163,7 +5163,7 @@ body.explorer-open .explorer-close {
-webkit-transform: none;
-ms-transform: none;
transform: none;
- padding-left: 180px;
+ padding-left: 220px;
}
.nav-wrapper {
position: absolute;
@@ -5174,7 +5174,7 @@ body.explorer-open .explorer-close {
.nav-wrapper .inner {
height: 100%;
position: fixed;
- width: 180px;
+ width: 220px;
}
.nav-toggle {
display: none;
@@ -5186,7 +5186,7 @@ body.explorer-open .explorer-close {
}
.nav-main .footer {
position: fixed;
- width: 180px;
+ width: 220px;
bottom: 0;
}
.nav-main .footer-open .footer-submenu {
@@ -5256,14 +5256,14 @@ body.explorer-open .explorer-close {
width: 0;
padding: 0;
top: 0;
- left: 180px;
+ left: 220px;
overflow: auto;
max-height: 100%;
}
.nav-submenu h2,
.nav-submenu ul {
float: right;
- width: 180px;
+ width: 220px;
}
.nav-submenu h2 {
display: block;
@@ -5296,14 +5296,14 @@ body.explorer-open .explorer-close {
}
li.submenu-active .nav-submenu {
box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35);
- width: 180px;
- padding: 0 0 1.5em;
+ width: 220px;
+ padding: 0 0 0.5em;
}
body.ready li.submenu-active .nav-submenu {
transition: width 0.2s ease;
}
li.submenu-active .nav-submenu a {
- padding-left: 3.5em;
+ padding-left: 1em;
}
.explorer {
width: 400px;
@@ -5325,7 +5325,7 @@ body.explorer-open .explorer-close {
}
body.explorer-open .nav-wrapper {
margin-left: 0;
- width: 180px;
+ width: 220px;
}
body.explorer-open .explorer:before {
display: none;
@@ -5356,7 +5356,7 @@ body.explorer-open .explorer-close {
-webkit-transform: none;
-ms-transform: none;
transform: none;
- left: 180px;
+ left: 220px;
position: relative;
}
body.explorer-open .wrapper {
@@ -5375,7 +5375,7 @@ body.explorer-open .explorer-close {
left: 0;
}
body.explorer-open .nav-wrapper {
- width: 180px;
+ width: 220px;
}
}
@@ -5603,7 +5603,7 @@ body.explorer-open .explorer-close {
position: absolute;
z-index: 25;
top: 0;
- left: 180px;
+ left: 220px;
}
#wagtail .c-state-indicator {
@@ -5811,7 +5811,7 @@ footer li {
}
footer .actions {
- width: 250px;
+ width: 220px;
margin-right: 1.5%;
}
diff --git a/aircox_cms/templates/aircox_cms/diffusion_page.html b/aircox_cms/templates/aircox_cms/diffusion_page.html
index 5a9625c..8ea064e 100755
--- a/aircox_cms/templates/aircox_cms/diffusion_page.html
+++ b/aircox_cms/templates/aircox_cms/diffusion_page.html
@@ -18,6 +18,7 @@
{% endif %}
{% endwith %}
+{% if diffusion.diffusion_set.count %}
{% trans "Dates of diffusion" %}
@@ -29,6 +30,7 @@
{% endwith %}
+{% endif %}
{% with podcasts=self.get_podcasts %}
{% if podcasts %}
diff --git a/aircox_cms/templates/aircox_cms/program_page.html b/aircox_cms/templates/aircox_cms/program_page.html
index 9eb5af5..bfa7d46 100755
--- a/aircox_cms/templates/aircox_cms/program_page.html
+++ b/aircox_cms/templates/aircox_cms/program_page.html
@@ -8,7 +8,7 @@
{% block content_extras %}
-{% if page.program.active %}
+{% if page.program.active and page.program.schedule_set.count %}
{% trans "Schedule" %}
{% for schedule in page.program.schedule_set.all %}
diff --git a/aircox_cms/wagtail_hooks.py b/aircox_cms/wagtail_hooks.py
index 9238c1e..3fc0510 100755
--- a/aircox_cms/wagtail_hooks.py
+++ b/aircox_cms/wagtail_hooks.py
@@ -29,6 +29,7 @@ class DiffusionAdmin(ModelAdmin):
menu_order = 200
list_display = ('program', 'start', 'end', 'type', 'initial')
list_filter = ('program', 'start', 'type')
+ readonly_fields = ('conflicts',)
search_fields = ('program__name', 'start')
class ScheduleAdmin(ModelAdmin):
@@ -99,12 +100,21 @@ class GenericMenu(Menu):
super().__init__('')
def get_queryset(self):
+ """
+ Return a queryset of items used to display menu
+ """
pass
def get_title(self, item):
+ """
+ Return the title of a menu-item for the given item
+ """
pass
def get_parent(self, item):
+ """
+ Return id of the parent page for the given item
+ """
pass
def get_page_url(self, page_model, item):
@@ -150,7 +160,12 @@ class DiffusionsMenu(GenericMenu):
).order_by('start')
def get_title(self, item):
- return item.program.name
+ from django.utils.safestring import mark_safe
+ title = '{} {}'.format(
+ item.start.strftime('%H:%M'),
+ item.program.name
+ )
+ return mark_safe(title)
def get_parent(self, item):
return item.program.page.first()
diff --git a/notes.md b/notes.md
index 383a247..dac3e79 100755
--- a/notes.md
+++ b/notes.md
@@ -1,6 +1,21 @@
This file is used as a reminder, can be used as crappy documentation too.
+# for the 1.0
+- sounds:
+ - detect id3 tags
+- run tests:
+ - streamer: dealer & streams hours (to localtime)
+ x diffusions: update & check algorithms
+ x check in templates
+ x diffusion page date info
+- streamer:
+ - restart running streamer on demand
+ - add restart button for the streamer
+- cms:
+ - button to select the current station
+
+
# conventions
## coding style
* name of classes relative to a class:
@@ -40,11 +55,6 @@ cms:
- comments -> remove/edit by the author
# Timezone shit:
-- run tests:
- - streamer: dealer & streams hours (to localtime)
- - diffusions: update & check algorithms
- x check in templates
- x diffusion page date info
# Instance's TODO
- menu_top .sections: