From 021b2a116ab2f07283e6780179e5c7d5710f75e1 Mon Sep 17 00:00:00 2001 From: bkfox Date: Wed, 10 Aug 2016 00:58:05 +0200 Subject: [PATCH] work on admin interface, player, list of sounds --- cms/management/commands/programs_to_cms.py | 2 +- cms/models.py | 37 ++- cms/sections.py | 22 +- cms/static/cms/css/layout.css | 88 +++--- cms/static/cms/css/theme.css | 62 +++- cms/static/cms/js/player.js | 288 +++++++++++------- cms/templates/cms/base_site.html | 5 + cms/templates/cms/diffusion_page.html | 24 +- cms/templates/cms/publication.html | 17 +- cms/templates/cms/sections/section_list.html | 5 +- .../cms/sections/section_player.html | 61 +--- .../sections/section_publication_info.html | 2 + cms/templates/cms/snippets/list.html | 2 + cms/templates/cms/snippets/player.html | 45 +++ .../cms/snippets/sound_list_item.html | 29 +- cms/wagtail_hooks.py | 107 ++++++- controllers/views.py | 2 +- notes.md | 2 +- programs/models.py | 64 ++-- 19 files changed, 566 insertions(+), 298 deletions(-) create mode 100644 cms/templates/cms/snippets/player.html diff --git a/cms/management/commands/programs_to_cms.py b/cms/management/commands/programs_to_cms.py index b71bc2c..ddeac21 100644 --- a/cms/management/commands/programs_to_cms.py +++ b/cms/management/commands/programs_to_cms.py @@ -62,7 +62,7 @@ class Command (BaseCommand): start__gt = tz.now().date() - tz.timedelta(days = 20), page__isnull = True, initial__isnull = True - ) + ).exclude(type = Diffusion.Type.unconfirmed) for diffusion in qs: if not diffusion.program.page.count(): if not hasattr(diffusion.program, '__logged_diff_error'): diff --git a/cms/models.py b/cms/models.py index 36c639a..67baf23 100644 --- a/cms/models.py +++ b/cms/models.py @@ -31,6 +31,7 @@ import aircox.programs.models as programs import aircox.controllers.models as controllers import aircox.cms.settings as settings +from aircox.cms.utils import image_url from aircox.cms.sections import * @@ -163,6 +164,15 @@ class Comment(models.Model): _('comment'), ) + + def __str__(self): + # Translators: text shown in the comments list (in admin) + return _('{date}, {author}: {content}...').format( + author = self.author, + date = self.date.strftime('%d %A %Y, %H:%M'), + content = self.content[:128] + ) + def make_safe(self): self.author = bleach.clean(self.author, tags=[]) if self.email: @@ -263,6 +273,15 @@ class Publication(Page): index.FilterField('show_in_menus'), ] + + @property + def icon(self): + return image_url(self.cover, 'fill-64x64') + + @property + def small_icon(self): + return image_url(self.cover, 'fill-32x32') + @property def recents(self): return self.get_children().type(Publication).not_in_menu().live() \ @@ -390,10 +409,8 @@ class Track(programs.Track,Orderable): diffusion = ParentalKey('DiffusionPage', related_name='tracks') panels = [ - FieldRowPanel([ - FieldPanel('artist'), - FieldPanel('title'), - ]), + FieldPanel('artist'), + FieldPanel('title'), FieldPanel('tags'), FieldPanel('info'), ] @@ -428,13 +445,15 @@ class DiffusionPage(Publication): verbose_name = _('Diffusion') verbose_name_plural = _('Diffusions') - content_panels = [ - FieldPanel('diffusion'), - FieldPanel('publish_archive'), - ] + Publication.content_panels + [ + content_panels = Publication.content_panels + [ InlinePanel('tracks', label=_('Tracks')), ] + promote_panels = [ + # FieldPanel('diffusion'), + FieldPanel('publish_archive'), + ] + Publication.promote_panels + @classmethod def from_diffusion(cl, diff, model = None, **kwargs): model = model or cl @@ -519,8 +538,6 @@ class DiffusionPage(Publication): podcast.public = publish podcast.save() - - super().save(*args, **kwargs) diff --git a/cms/sections.py b/cms/sections.py index 5a239a9..8c3b270 100644 --- a/cms/sections.py +++ b/cms/sections.py @@ -221,19 +221,18 @@ class ListBase(models.Model): else: qs = qs.descendant_of(related) - date = self.related.date if hasattr('date', related) else \ + date = self.related.date if hasattr(related, 'date') else \ self.related.first_published_at if self.date_filter == self.DateFilter.before_related: qs = qs.filter(date__lt = date) elif self.date_filter == self.DateFilter.after_related: qs = qs.filter(date__gte = date) # date - else: - date = tz.now() - if self.date_filter == self.DateFilter.previous: - qs = qs.filter(date__lt = date) - elif self.date_filter == self.DateFilter.next: - qs = qs.filter(date__gte = date) + date = tz.now() + if self.date_filter == self.DateFilter.previous: + qs = qs.filter(date__lt = date) + elif self.date_filter == self.DateFilter.next: + qs = qs.filter(date__gte = date) # sort if self.asc: @@ -332,9 +331,7 @@ class ListBase(models.Model): search = request.GET.get('search') if search: kwargs['terms'] = search - print(search, qs) qs = qs.search(search) - print(qs.count()) set('list_selector', kwargs) @@ -342,7 +339,7 @@ class ListBase(models.Model): if qs: paginator = Paginator(qs, 30) try: - qs = paginator.page('page') + qs = paginator.page(request.GET.get('page') or 1) except PageNotAnInteger: qs = paginator.page(1) except EmptyPage: @@ -803,7 +800,7 @@ class SectionList(ListBase, SectionRelativeItem): 'list. If empty, does not print an address'), ) - panels = SectionItem.panels + [ + panels = SectionRelativeItem.panels + [ MultiFieldPanel([ FieldPanel('focus_available'), FieldPanel('count'), @@ -815,6 +812,9 @@ class SectionList(ListBase, SectionRelativeItem): from aircox.cms.models import Publication context = super().get_context(request, page) + if self.is_related: + self.related = page + qs = self.get_queryset() qs = qs.live() if self.focus_available: diff --git a/cms/static/cms/css/layout.css b/cms/static/cms/css/layout.css index 78c7d02..394fd44 100644 --- a/cms/static/cms/css/layout.css +++ b/cms/static/cms/css/layout.css @@ -24,6 +24,9 @@ ul { float: left; } +.small { + font-size: 0.8em; +} .icon { max-width: 2em; @@ -67,9 +70,11 @@ nav.menu { padding: 0.2em; height: 2.5em; margin-bottom: 1em; + background-color: white; + box-shadow: 0em 0em 0.2em black; } .menu.top * { - vertical-align: middle; + vertical-align: bottom; } .menu.top > section { @@ -81,6 +86,10 @@ nav.menu { margin: 0.2em 1em; } +.page_left, .page_right { + max-width: 16em; +} + .page_left > section, .page_right > section { margin-bottom: 1em; @@ -121,6 +130,12 @@ ul.list { } +.list nav { + text-align: center; + font-size: 0.9em; +} + + /** content: date list **/ .date_list nav { text-align:center; @@ -188,8 +203,6 @@ ul.list { font-size: 0.8em; } - - .comments ul { margin-top: 2.5em; } @@ -208,19 +221,23 @@ ul.list { float: right; } + /** content: player **/ .player { - width: 20em; } - .player:not([seekable]) > .controls { + .player:not([seekable]) > .controls > .progress { display: none; } -.player .controls > * { - margin: 0em 0.2em; +.player .controls { + margin-top: 1em; + text-align: right; } + .player .controls > * { + margin: 0em 0.2em; + } .player .controls .single { display: none; @@ -245,14 +262,9 @@ ul.list { border-right: 2px #818181 solid; } -.player .on_air a:not([href]), .on_air a[href=""] { - display: none; -} - .player .playlist .item { margin: 0em; padding: 0.2em 0.4em; - height: 1em; cursor: pointer; } @@ -266,25 +278,20 @@ ul.list { .player .playlist .item .actions { display: none; + font-size: 0.9em; } .player .playlist .item:hover .actions { - display: inline-block; + display: inline; } - .player .playlist .item .info { - float: right; - width: 2em; - display: inline-block; - } - - .player .item:not([selected]) .button { - display: none; - } .player .item[selected] { - height: auto; - font-size: 1.1em; + border-left: 1px #007EDF solid; + font-size: 1.0em; +} + +.player .item:not([selected]) { } .player .button { @@ -300,36 +307,33 @@ ul.list { max-height: 2.0em; } - .player:not([state]) .button > img:not(.play), - .player[state="paused"] .button > img:not(.play), - .player[state="playing"] .button > img:not(.pause), - .player[state="loading"] .button > img:not(.loading) + + .player:not([state]) .item[selected] .button > img:not(.play), + .player[state="paused"] .item[selected] .button > img:not(.play), + .player[state="playing"] .item[selected] .button > img:not(.pause), + .player[state="loading"] .item[selected] .button > img:not(.loading) { display: none; } - .player[state="loading"] .box .button > img.loading { - animation-duration: 2s; - animation-iteration-count: infinite; - animation-name: rotate; - animation-timing-function: linear; + .player .item:not([selected]) .button > img.play { + display: block; + } + .player .item:not([selected]) .button > img:not(.play) { + display: none; } - @keyframes rotate { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } - } +.player .list_item.live:hover .actions { + display: none; +} /** content: page **/ main .body ~ section:not(.comments) { width: calc(50% - 1em); - float: left; + vertical-align: top; + display: inline-block; } diff --git a/cms/static/cms/css/theme.css b/cms/static/cms/css/theme.css index 3476e63..eb8c8f2 100644 --- a/cms/static/cms/css/theme.css +++ b/cms/static/cms/css/theme.css @@ -50,6 +50,7 @@ time { .info { font-size: 0.9em; + padding: 0.1em; color: #007EDF; } @@ -81,14 +82,69 @@ main { box-shadow: 0em 0em 0.2em black; } - main h1 { - margin: 0em; + main h1:not(.detail_title) { margin: 0em 0em 0.4em 0em; } - main .content img.cover { + + main h1.detail_title { + margin: 0em; + padding: 0.2em; + position: relative; + left: -0.7em; + width: 80%; + background-color: rgba(255,255,255,0.8); + } + + main img.detail_cover { width: calc(100% + 2em); + margin-top: -3.3em; margin-left: -1em; } + +/** player **/ +.player[state='playing'] .item[selected] .button > img { + animation-duration: 4s; + animation-iteration-count: infinite; + animation-name: blink; +} + + +@keyframes blink { + from { + opacity: 1.0; + } + + 50% { + opacity: 0.3; + } + + to { + opacity: 1.0; + } +} + + +.player[state="loading"] .item[selected] .button > img.loading { + animation-duration: 2s; + animation-iteration-count: infinite; + animation-name: rotate; + animation-timing-function: linear; +} + +@keyframes rotate { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + + + + + diff --git a/cms/static/cms/js/player.js b/cms/static/cms/js/player.js index 6e89b68..eae96cd 100644 --- a/cms/static/cms/js/player.js +++ b/cms/static/cms/js/player.js @@ -18,11 +18,13 @@ function duration_str(seconds) { } -function Sound(title, detail, duration, streams) { +function Sound(title, detail, duration, streams, cover, on_air) { this.title = title; this.detail = detail; this.duration = duration; this.streams = streams.splice ? streams.sort() : [streams]; + this.cover = cover; + this.on_air = on_air; } Sound.prototype = { @@ -30,25 +32,60 @@ Sound.prototype = { detail: '', streams: undefined, duration: undefined, - on_air_url: undefined, + cover: undefined, + on_air: false, item: undefined, get seekable() { return this.duration != undefined; }, + + make_item: function(playlist, base_item) { + if(this.item) + return; + + var item = base_item.cloneNode(true); + item.removeAttribute('style'); + + item.querySelector('.title').innerHTML = this.title; + if(this.seekable) + item.querySelector('.duration').innerHTML = + duration_str(this.duration); + if(this.detail) + item.querySelector('.detail').href = this.detail; + if(playlist.player.show_cover && this.cover) + item.querySelector('img.play').src = this.cover; + + item.sound = this; + this.item = item; + + // events + var self = this; + item.querySelector('.action.remove').addEventListener( + 'click', function(event) { playlist.remove(self); }, false + ); + + item.addEventListener('click', function(event) { + if(event.target.className.indexOf('action') != -1) + return; + playlist.select(self, true) + }, false); + }, } -function PlayerPlaylist(player) { +function Playlist(player) { this.player = player; this.playlist = player.player.querySelector('.playlist'); this.item_ = player.player.querySelector('.playlist .item'); this.sounds = [] } -PlayerPlaylist.prototype = { +Playlist.prototype = { + on_air: undefined, sounds: undefined, + sound: undefined, /// Find a sound by its streams, and return it if found find: function(streams) { @@ -71,30 +108,12 @@ PlayerPlaylist.prototype = { if(sound_) return sound_; - var item = this.item_.cloneNode(true); - item.removeAttribute('style'); + if(sound.on_air) + this.on_air = 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; + sound.make_item(this, this.item_); + (container || this.playlist).appendChild(sound.item); - item.sound = sound; - sound.item = item; - - var self = this; - item.querySelector('.action.remove').addEventListener( - 'click', function(event) { self.remove(sound); }, false - ); - item.addEventListener('click', function(event) { - if(event.target.className.indexOf('action') != -1) - return; - self.player.select(sound, true) - }, false); - - (container || this.playlist).appendChild(item); this.sounds.push(sound); this.save(); return sound; @@ -106,8 +125,61 @@ PlayerPlaylist.prototype = { this.sounds.splice(index,1); this.playlist.removeChild(sound.item); this.save(); + + this.player.stop() + this.next(false); }, + select: function(sound, play = true) { + this.player.playlist = this; + if(this.sound == sound) { + if(play) + this.player.play(); + return; + } + + if(this.sound) + this.unselect(this.sound); + this.sound = sound; + + // audio + this.player.load_sound(this.sound); + + // attributes + var container = this.player.player; + sound.item.setAttribute('selected', 'true'); + + if(!sound.on_air) + sound.item.querySelector('.content').insertBefore( + this.player.progress.item, + sound.item.querySelector('.content .duration') + ) + + if(sound.seekable) + container.setAttribute('seekable', 'true'); + else + container.removeAttribute('seekable'); + + // play + if(play) + this.player.play(); + }, + + unselect: function(sound) { + sound.item.removeAttribute('selected'); + }, + + next: function(play = true) { + var index = this.sounds.indexOf(this.sound); + if(index < 0) + return; + + index++; + if(index < this.sounds.length) + this.select(this.sounds[index]); + }, + + // storage save: function() { var list = []; for(var i in this.sounds) { @@ -124,7 +196,7 @@ PlayerPlaylist.prototype = { for(var i in list) { var sound = list[i]; sound = new Sound(sound.title, sound.detail, sound.duration, - sound.streams) + sound.streams, sound.cover, sound.on_air) this.add(sound, container) } this.playlist.appendChild(container); @@ -132,53 +204,67 @@ PlayerPlaylist.prototype = { } -function Player(id) { - this.store = new Store('player'); +function Player(id, on_air_url, show_cover) { + this.id = id; + this.on_air_url = on_air_url; + this.show_cover = show_cover; + + this.store = new Store('player_' + id); // html sounds this.player = document.getElementById(id); this.audio = this.player.querySelector('audio'); this.on_air = this.player.querySelector('.on_air'); + this.progress = { + item: this.player.querySelector('.controls .progress'), + bar: this.player.querySelector('.controls .progress progress'), + duration: this.player.querySelector('.controls .progress .duration') + } + console.log(this.progress) + this.controls = { - duration: this.player.querySelector('.controls .duration'), - progress: this.player.querySelector('progress'), single: this.player.querySelector('input.single'), } - this.playlist = new PlayerPlaylist(this); + this.playlist = new Playlist(this); this.playlist.load(); this.init_events(); this.load(); + + this.update_on_air(); } Player.prototype = { /// current item being played sound: undefined, + on_air_url: undefined, init_events: function() { var self = this; function time_from_progress(event) { - bounding = self.controls.progress.getBoundingClientRect() + bounding = self.progress.bar.getBoundingClientRect() offset = (event.clientX - bounding.left); return offset * self.audio.duration / bounding.width; } function update_info() { - var controls = self.controls; + var progress = self.progress; + var pos = self.audio.currentTime; + // progress if(!self.sound || !self.sound.seekable || - self.audio.duration == Infinity) { - controls.duration.innerHTML = ''; - controls.progress.value = 0; + !pos || self.audio.duration == Infinity) + { + progress.duration.innerHTML = ''; + progress.bar.value = 0; return; } - var pos = self.audio.currentTime; - controls.progress.value = pos; - controls.progress.max = self.audio.duration; - controls.duration.innerHTML = duration_str(pos); + progress.bar.value = pos; + progress.bar.max = self.audio.duration; + progress.duration.innerHTML = duration_str(pos); } // audio @@ -207,7 +293,7 @@ Player.prototype = { }, false); // progress - progress = this.controls.progress; + progress = this.progress.bar; progress.addEventListener('click', function(event) { player.audio.currentTime = time_from_progress(event); }, false); @@ -215,43 +301,55 @@ Player.prototype = { progress.addEventListener('mouseout', update_info, false); progress.addEventListener('mousemove', function(event) { - if(self.audio.duration == Infinity) + if(self.audio.duration == Infinity || isNaN(self.audio.duration)) return; var pos = time_from_progress(event); - self.controls.duration.innerHTML = duration_str(pos); + self.progress.duration.innerHTML = duration_str(pos); }, false); }, - update_on_air: function(url) { - if(!url) { - // TODO HERE - } + update_on_air: function() { + if(!this.on_air_url) + return; var self = this; + window.setTimeout(function() { + self.update_on_air(); + }, 60*1000); + + if(!this.playlist.on_air) + return; + var req = new XMLHttpRequest(); - req.open('GET', url, true); + req.open('GET', this.on_air_url, true); req.onreadystatechange = function() { - if(req.readyState != 4 || (req.status != 200 && req.status != 0)) + if(req.readyState != 4 || (req.status != 200 && + req.status != 0)) return; var data = JSON.parse(req.responseText) - if(data.type == 'track') { - self.on_air.querySelector('.info').innerHTML = '♫'; - self.on_air.querySelector('.title') = - (data.artist || '') + ' — ' + (data.title); - self.on_air.querySelector('.url').removeAttribute('href'); - } - else { - self.on_air.querySelector('.info').innerHTML = ''; - self.on_air.querySelector('.title').innerHTML = data.title; - self.on_air.querySelector('.url').setAttribute('href', data.url); - } + if(data.type == 'track') + data = { + title: '♫' + (data.artist ? data.artist + ' — ' : '') + + data.title, + url: '' + } + else + data = { + title: data.title, + info: '', + url: data.url + } - if(timeout) - window.setTimeout(function() { - self.update_on_air(url); - }, 60*1000); + var on_air = self.playlist.on_air; + on_air = on_air.item.querySelector('.content'); + + if(data.url) + on_air.innerHTML = + '' + data.title + ''; + else + on_air.innerHTML = data.title; }; req.send(); }, @@ -263,78 +361,44 @@ Player.prototype = { this.audio.pause(); }, - unselect: function(sound) { - sound.item.removeAttribute('selected'); - }, - __mime_type: function(path) { ext = path.substr(path.lastIndexOf('.')+1); return 'audio/' + ext; }, - select: function(sound, play = true) { - if(this.sound == sound) { - if(play) - this.play(); - return; - } + load_sound: function(sound) { + var audio = this.audio; + audio.pause(); - if(this.sound) - this.unselect(this.sound); - - this.audio.pause(); - - // streams as - var sources = this.audio.querySelectorAll('source'); - for(var i = 0; i < sources.length; i++) { - this.audio.removeChild(sources[i]); - } + var sources = audio.querySelectorAll('source'); + for(var i = 0; i < sources.length; i++) + audio.removeChild(sources[i]); streams = sound.streams; for(var i = 0; i < streams.length; i++) { var source = document.createElement('source'); source.src = streams[i]; source.type = this.__mime_type(source.src); - this.audio.appendChild(source); + audio.appendChild(source); } - this.audio.load(); - - // attributes - this.sound = sound; - sound.item.setAttribute('selected', 'true'); - - if(sound.seekable) - this.player.setAttribute('seekable', 'true'); - else - this.player.removeAttribute('seekable'); - - // play - if(play) - this.play(); - }, - - next: function() { - var index = this.playlist.sounds.indexOf(this.sound); - if(index < 0) - return; - - index++; - if(index < this.playlist.sounds.length) - this.select(this.playlist.sounds[index], true); + audio.load(); }, save: function() { + // TODO: move stored sound into playlist this.store.set('player', { single: this.controls.single.checked, - sound: this.sound && this.sound.streams, + sound: this.playlist.sound && this.playlist.sound.streams, }); }, load: function() { var data = this.store.get('player'); + if(!data) + return; this.controls.single.checked = data.single; if(data.sound) - this.sound = this.playlist.find(data.sound); + this.playlist.sound = this.playlist.find(data.sound); }, } diff --git a/cms/templates/cms/base_site.html b/cms/templates/cms/base_site.html index 664524d..3d7fa5a 100644 --- a/cms/templates/cms/base_site.html +++ b/cms/templates/cms/base_site.html @@ -67,8 +67,13 @@ + {% block footer %} + {% endblock %} diff --git a/cms/templates/cms/diffusion_page.html b/cms/templates/cms/diffusion_page.html index 96a22ab..cae92db 100644 --- a/cms/templates/cms/diffusion_page.html +++ b/cms/templates/cms/diffusion_page.html @@ -34,16 +34,26 @@ {% if podcasts %}

{% trans "Podcasts" %}

- {% for item in podcasts %} - {% include 'cms/snippets/sound_list_item.html' %} - {% endfor %} +
+ {% include 'cms/snippets/player.html' %} + + +
{% endif %} {% endwith %} - -{# TODO: podcasts #} {% endblock %} - - diff --git a/cms/templates/cms/publication.html b/cms/templates/cms/publication.html index d1aab0b..4555849 100644 --- a/cms/templates/cms/publication.html +++ b/cms/templates/cms/publication.html @@ -6,23 +6,30 @@ {% load aircox_cms %} +{% if not object_list %} +{% block title %} +

{{ page.title }}

+{% endblock %} +{% endif %} + {% block content %} {% if object_list %} {# list view #} -
+
{{ page.summary }} {% trans "Go back to the publication" %} -
+ {% with list_paginator=paginator %} {% include "cms/snippets/list.html" %} {% endwith %} {% else %} {# detail view #} + {% if page.cover %} + {% image page.cover max-600x480 class="detail_cover cover" height="" width="" %} + {% endif %} +
- {% if page.cover %} - {% image page.cover max-600x480 class="cover" height="" width="" %} - {% endif %}
{{ page.body|richtext}}
diff --git a/cms/templates/cms/sections/section_list.html b/cms/templates/cms/sections/section_list.html index 999aa75..43a6d67 100644 --- a/cms/templates/cms/sections/section_list.html +++ b/cms/templates/cms/sections/section_list.html @@ -7,11 +7,10 @@ {% endwith %} {% endif %} +{% with url=url url_text=self.url_text %} {% include "cms/snippets/list.html" %} +{% endwith %} -{% if url %} - -{% endif %} {% endblock %} diff --git a/cms/templates/cms/sections/section_player.html b/cms/templates/cms/sections/section_player.html index b28def3..9902158 100644 --- a/cms/templates/cms/sections/section_player.html +++ b/cms/templates/cms/sections/section_player.html @@ -1,59 +1,24 @@ {% extends 'cms/sections/section_item.html' %} -{% load staticfiles %} -{% load i18n %} - {% block content %} -
- - -
- - - - - -
- -
- -
+ {% include "cms/snippets/player.html" %}
{% endblock %} diff --git a/cms/templates/cms/sections/section_publication_info.html b/cms/templates/cms/sections/section_publication_info.html index 793bf30..123aac4 100644 --- a/cms/templates/cms/sections/section_publication_info.html +++ b/cms/templates/cms/sections/section_publication_info.html @@ -1,6 +1,7 @@ {% extends "cms/sections/section_item.html" %} {% load i18n %} +{% block content %}
{% if page.publish_as %} @@ -24,4 +25,5 @@ {% endfor %}
+{% endblock %} diff --git a/cms/templates/cms/snippets/list.html b/cms/templates/cms/snippets/list.html index 97abb4e..901f4a9 100644 --- a/cms/templates/cms/snippets/list.html +++ b/cms/templates/cms/snippets/list.html @@ -56,6 +56,8 @@ Options: {% endif %} {% endwith %} +{% elif url and url_text %} + {% endif %} diff --git a/cms/templates/cms/snippets/player.html b/cms/templates/cms/snippets/player.html new file mode 100644 index 0000000..b3054ff --- /dev/null +++ b/cms/templates/cms/snippets/player.html @@ -0,0 +1,45 @@ +{% load staticfiles %} +{% load i18n %} + + + +
+ +
+ +
+ + + + + + + +
+ diff --git a/cms/templates/cms/snippets/sound_list_item.html b/cms/templates/cms/snippets/sound_list_item.html index 30402fc..241a6ba 100644 --- a/cms/templates/cms/snippets/sound_list_item.html +++ b/cms/templates/cms/snippets/sound_list_item.html @@ -1,22 +1,37 @@ {% load static %} +{% load i18n %} {# TODO: complete archive podcast -> info #} - + + + {% trans + diff --git a/cms/wagtail_hooks.py b/cms/wagtail_hooks.py index 55937ee..8795242 100644 --- a/cms/wagtail_hooks.py +++ b/cms/wagtail_hooks.py @@ -1,14 +1,84 @@ from django.utils import timezone as tz from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse +from django.contrib.staticfiles.templatetags.staticfiles import static +from django.utils.html import format_html from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem, Menu, SubmenuMenuItem +from wagtail.contrib.modeladmin.options import \ + ModelAdmin, ModelAdminGroup, modeladmin_register + + import aircox.programs.models as programs +import aircox.cms.models as models + + +class ProgramAdmin(ModelAdmin): + model = programs.Program + menu_label = _('Programs') + menu_icon = 'pick' + menu_order = 200 + list_display = ('name', 'active') + search_fields = ('name',) + +class DiffusionAdmin(ModelAdmin): + model = programs.Diffusion + menu_label = _('Diffusions') + menu_icon = 'date' + menu_order = 200 + list_display = ('program', 'start', 'end', 'frequency', 'initial') + list_filter = ('frequency', 'start', 'program') + +class ScheduleAdmin(ModelAdmin): + model = programs.Schedule + menu_label = _('Schedules') + menu_icon = 'time' + menu_order = 200 + list_display = ('program', 'frequency', 'duration', 'initial') + list_filter = ('frequency', 'date', 'duration', 'program') + +class StreamAdmin(ModelAdmin): + model = programs.Stream + menu_label = _('Streams') + menu_icon = 'time' + menu_order = 200 + list_display = ('program', 'delay', 'begin', 'end') + list_filter = ('program', 'delay', 'begin', 'end') + +class AdvancedAdminGroup(ModelAdminGroup): + menu_label = _("Advanced") + menu_icon = 'plus-inverse' + items = (ProgramAdmin, DiffusionAdmin, ScheduleAdmin, StreamAdmin) + +modeladmin_register(AdvancedAdminGroup) + + +class SoundAdmin(ModelAdmin): + model = programs.Sound + menu_label = _('Sounds') + menu_icon = 'media' + menu_order = 350 + list_display = ('name', 'duration', 'type', 'path', 'good_quality', 'public') + list_filter = ('type', 'good_quality', 'public') + search_fields = ('name', 'path') + +modeladmin_register(SoundAdmin) + + +## Hooks + +@hooks.register('insert_editor_css') +def editor_css(): + return format_html( + '', + static('cms/css/cms.css') + ) + class GenericMenu(Menu): - last_time = None + page_model = models.Publication def __init__(self): super().__init__('') @@ -19,40 +89,47 @@ class GenericMenu(Menu): def get_title(self, item): pass - def get_parent_page(self, item): + def get_parent(self, item): pass - def get_page_url(self, item): + def get_page_url(self, page_model, item): if item.page.count(): return reverse('wagtailadmin_pages:edit', args=[item.page.first().id]) - parent_page = self.get_parent_page(item) + + parent_page = self.get_parent(item) if not parent_page: return '' - return reverse('wagtailadmin_pages:add_subpage', args=[parent_page.id]) + + return reverse( + 'wagtailadmin_pages:add', args= [ + page_model._meta.app_label, page_model._meta.model_name, + parent_page.id + ] + ) @property def registered_menu_items(self): now = tz.now() last_max = now - tz.timedelta(minutes = 10) - if self._registered_menu_items is None or self.last_time < last_max: - qs = self.get_queryset() - self._registered_menu_items = [ - MenuItem(self.get_title(x), self.get_page_url(x)) - for x in qs - ] - self.last_time = now - return self._registered_menu_items + qs = self.get_queryset() + return [ + MenuItem(self.get_title(x), self.get_page_url(self.page_model, x)) + for x in qs + ] class DiffusionsMenu(GenericMenu): """ Menu to display diffusions of today """ + page_model = models.DiffusionPage + def get_queryset(self): return programs.Diffusion.objects.filter( type = programs.Diffusion.Type.normal, start__contains = tz.now().date(), + initial__isnull = True, ).order_by('start') def get_title(self, item): @@ -74,6 +151,8 @@ class ProgramsMenu(GenericMenu): """ Menu to display all active programs. """ + page_model = models.DiffusionPage + def get_queryset(self): return programs.Program.objects \ .filter(active = True, page__isnull = False) \ @@ -92,7 +171,6 @@ class ProgramsMenu(GenericMenu): return settings.default_program_parent_page - @hooks.register('register_admin_menu_item') def register_programs_menu_item(): return SubmenuMenuItem( @@ -101,4 +179,3 @@ def register_programs_menu_item(): ) - diff --git a/controllers/views.py b/controllers/views.py index 780cfed..5280aca 100644 --- a/controllers/views.py +++ b/controllers/views.py @@ -64,7 +64,7 @@ def on_air(request): last = { 'type': 'diffusion', - 'title': publication.title if publication else last.program.name, + 'title': last.program.name, 'date': last.start, 'url': publication.specific.url if publication else None, } diff --git a/notes.md b/notes.md index fecc87d..c02110b 100644 --- a/notes.md +++ b/notes.md @@ -26,10 +26,10 @@ This file is used as a reminder, can be used as crappy documentation too. - controllers : - models to template -> note + - input stream - streamed program disable -> remote control on liquidsoap - tests: - monitor - - check when a played sound has a temp blank - config generation and sound diffusion - cms: diff --git a/programs/models.py b/programs/models.py index 5922727..6179d31 100755 --- a/programs/models.py +++ b/programs/models.py @@ -268,38 +268,6 @@ class Sound(Nameable): verbose_name_plural = _('Sounds') -class Stream(models.Model): - """ - When there are no program scheduled, it is possible to play sounds - in order to avoid blanks. A Stream is a Program that plays this role, - and whose linked to a Stream. - - All sounds that are marked as good and that are under the related - program's archive dir are elligible for the sound's selection. - """ - program = models.ForeignKey( - 'Program', - verbose_name = _('related program'), - ) - delay = models.TimeField( - _('delay'), - blank = True, null = True, - help_text = _('delay between two sound plays') - ) - begin = models.TimeField( - _('begin'), - blank = True, null = True, - help_text = _('used to define a time range this stream is' - 'played') - ) - end = models.TimeField( - _('end'), - blank = True, null = True, - help_text = _('used to define a time range this stream is' - 'played') - ) - - class Schedule(models.Model): """ A Schedule defines time slots of programs' diffusions. It can be an initial @@ -636,6 +604,38 @@ class DiffusionManager(models.Manager): ).order_by('start') +class Stream(models.Model): + """ + When there are no program scheduled, it is possible to play sounds + in order to avoid blanks. A Stream is a Program that plays this role, + and whose linked to a Stream. + + All sounds that are marked as good and that are under the related + program's archive dir are elligible for the sound's selection. + """ + program = models.ForeignKey( + 'Program', + verbose_name = _('related program'), + ) + delay = models.TimeField( + _('delay'), + blank = True, null = True, + help_text = _('delay between two sound plays') + ) + begin = models.TimeField( + _('begin'), + blank = True, null = True, + help_text = _('used to define a time range this stream is' + 'played') + ) + end = models.TimeField( + _('end'), + blank = True, null = True, + help_text = _('used to define a time range this stream is' + 'played') + ) + + class Diffusion(models.Model): """ A Diffusion is an occurrence of a Program that is scheduled on the