forked from rc/aircox
		
	cms.actions + website.actions; Sounds section; player: bug fix (ask for restart on live stream), actions; remove website.Sound (not really used): move chmod/public into programs.Sound
This commit is contained in:
		
							
								
								
									
										126
									
								
								cms/actions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								cms/actions.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					Actions are used to add controllers available to the end user.
 | 
				
			||||||
 | 
					They are attached to models, and tested (+ rendered if it is okay)
 | 
				
			||||||
 | 
					before rendering each instance of the models.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For the moment it only can execute javascript code. There is also
 | 
				
			||||||
 | 
					a javascript mini-framework in order to make it easy. The code of
 | 
				
			||||||
 | 
					the action is then registered and executable on users actions.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					class Actions(type):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    General class that is used to register and manipulate actions
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    registry = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __new__(cls, name, bases, attrs):
 | 
				
			||||||
 | 
					        cl = super().__new__(cls, name, bases, attrs)
 | 
				
			||||||
 | 
					        if name != 'Action':
 | 
				
			||||||
 | 
					            cls.registry.append(cl)
 | 
				
			||||||
 | 
					        return cl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def make(cl, request, object_list = None, object = None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Make action on the given object_list or object
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if object_list:
 | 
				
			||||||
 | 
					            in_list = True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            object_list = [object]
 | 
				
			||||||
 | 
					            in_list = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for object in object_list:
 | 
				
			||||||
 | 
					            if not hasattr(object, 'actions') or not object.actions:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            object.actions = [
 | 
				
			||||||
 | 
					                action.test(request, object, in_list)
 | 
				
			||||||
 | 
					                if type(action) == cl and issubclass(action, Action) else
 | 
				
			||||||
 | 
					                str(action)
 | 
				
			||||||
 | 
					                for action in object.actions
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            object.actions = [ code for code in object.actions if code ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def register_code(cl):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Render javascript code that can be inserted somewhere to register
 | 
				
			||||||
 | 
					        all actions
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return '\n'.join(action.register_code() for action in cl.registry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Action(metaclass=Actions):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    An action available to the end user.
 | 
				
			||||||
 | 
					    Don't forget to note in docstring the needed things.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    id = ''
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Unique ID for the given action
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    symbol = ''
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    UTF-8 symbol for the given action
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    title = ''
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Used to render the correct button for the given action
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    code = ''
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    If set, used as javascript code executed when the action is
 | 
				
			||||||
 | 
					    activated
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def register_code(cl):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Render a Javascript code that append the code to the available actions.
 | 
				
			||||||
 | 
					        Used by Actions
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not cl.code:
 | 
				
			||||||
 | 
					            return ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return """
 | 
				
			||||||
 | 
					        Actions.register('{cl.id}', '{cl.symbol}', '{cl.title}', {cl.code})
 | 
				
			||||||
 | 
					        """.format(cl = cl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def has_me(cl, object):
 | 
				
			||||||
 | 
					        return hasattr(object, 'actions') and cl.id in object.actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def to_str(cl, object, url = None, **data):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Utility class to add the action on the object using the
 | 
				
			||||||
 | 
					        given data.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if cl.has_me(object):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        code = \
 | 
				
			||||||
 | 
					            '<a class="action" {onclick} action="{cl.id}" {data} title="{cl.title}">' \
 | 
				
			||||||
 | 
					            '{cl.symbol}<label>{cl.title}</label>' \
 | 
				
			||||||
 | 
					            '</a>'.format(
 | 
				
			||||||
 | 
					                href = '' if not url else 'href="' + url + '"',
 | 
				
			||||||
 | 
					                onclick = 'onclick="return Actions.run(event, \'{cl.id}\');"' \
 | 
				
			||||||
 | 
					                              .format(cl = cl)
 | 
				
			||||||
 | 
					                          if cl.id and cl.code else '',
 | 
				
			||||||
 | 
					                data = ' '.join('data-{k}="{v}"'.format(k=k, v=v)
 | 
				
			||||||
 | 
					                                for k,v in data.items()),
 | 
				
			||||||
 | 
					                cl = cl
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def test(cl, request, object, in_list):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test if the given object can have the generated action. If yes, return
 | 
				
			||||||
 | 
					        the generated content, otherwise, return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        in_list: object is rendered in a list
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -171,6 +171,11 @@ class Post (models.Model, Routable):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    Fields on which routes.SearchRoute must run the search
 | 
					    Fields on which routes.SearchRoute must run the search
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					    actions = None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Actions are a list of actions available to the end user for this model.
 | 
				
			||||||
 | 
					    See aircox.cms.actions for more information
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_comments(self):
 | 
					    def get_comments(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,8 @@ from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			|||||||
from honeypot.decorators import check_honeypot
 | 
					from honeypot.decorators import check_honeypot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aircox.cms.forms import CommentForm
 | 
					from aircox.cms.forms import CommentForm
 | 
				
			||||||
import aircox.cms.decorators as decorators
 | 
					from aircox.cms.exposures import expose
 | 
				
			||||||
 | 
					from aircox.cms.actions import Actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Viewable:
 | 
					class Viewable:
 | 
				
			||||||
@ -51,7 +52,7 @@ class Viewable:
 | 
				
			|||||||
            setattr(Sub, k, v)
 | 
					            setattr(Sub, k, v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if hasattr(cl, '_exposure'):
 | 
					        if hasattr(cl, '_exposure'):
 | 
				
			||||||
            return decorators.expose(Sub)
 | 
					            return expose(Sub)
 | 
				
			||||||
        return Sub
 | 
					        return Sub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -224,6 +225,7 @@ class ListItem:
 | 
				
			|||||||
    image = None
 | 
					    image = None
 | 
				
			||||||
    info = None
 | 
					    info = None
 | 
				
			||||||
    url = None
 | 
					    url = None
 | 
				
			||||||
 | 
					    actions = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    css_class = None
 | 
					    css_class = None
 | 
				
			||||||
    attrs = None
 | 
					    attrs = None
 | 
				
			||||||
@ -285,7 +287,7 @@ class List(Section):
 | 
				
			|||||||
    def get_object_list(self):
 | 
					    def get_object_list(self):
 | 
				
			||||||
        return self.object_list
 | 
					        return self.object_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def prepare_object_list(self, object_list):
 | 
					    def prepare_list(self, object_list):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Prepare objects before context is sent to the template renderer.
 | 
					        Prepare objects before context is sent to the template renderer.
 | 
				
			||||||
        Return the object_list that is prepared.
 | 
					        Return the object_list that is prepared.
 | 
				
			||||||
@ -302,7 +304,7 @@ class List(Section):
 | 
				
			|||||||
            instances of Post or ListItem.
 | 
					            instances of Post or ListItem.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        If object_list is not given, call `get_object_list` to retrieve it.
 | 
					        If object_list is not given, call `get_object_list` to retrieve it.
 | 
				
			||||||
        Prepare the object_list using `self.prepare_object_list`.
 | 
					        Prepare the object_list using `self.prepare_list`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Set `request`, `object`, `object_list` and `kwargs` in self.
 | 
					        Set `request`, `object`, `object_list` and `kwargs` in self.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -314,10 +316,11 @@ class List(Section):
 | 
				
			|||||||
            object_list = self.object_list or self.get_object_list()
 | 
					            object_list = self.object_list or self.get_object_list()
 | 
				
			||||||
            if not object_list and not self.message_empty:
 | 
					            if not object_list and not self.message_empty:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
        self.object_list = object_list
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.object_list = object_list
 | 
				
			||||||
        if object_list:
 | 
					        if object_list:
 | 
				
			||||||
            object_list = self.prepare_object_list(object_list)
 | 
					            object_list = self.prepare_list(object_list)
 | 
				
			||||||
 | 
					            Actions.make(request, object_list = object_list)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        context = super().get_context_data(request, object, *args, **kwargs)
 | 
					        context = super().get_context_data(request, object, *args, **kwargs)
 | 
				
			||||||
        context.update({
 | 
					        context.update({
 | 
				
			||||||
@ -500,7 +503,7 @@ class Search(Section):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@decorators.expose
 | 
					@expose
 | 
				
			||||||
class Calendar(Section):
 | 
					class Calendar(Section):
 | 
				
			||||||
    model = None
 | 
					    model = None
 | 
				
			||||||
    template_name = "aircox/cms/calendar.html"
 | 
					    template_name = "aircox/cms/calendar.html"
 | 
				
			||||||
@ -535,11 +538,12 @@ class Calendar(Section):
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @decorators.expose
 | 
					    @expose
 | 
				
			||||||
    def render_exp(cl, *args, year, month, **kwargs):
 | 
					    def render_exp(cl, *args, year, month, **kwargs):
 | 
				
			||||||
        year = int(year)
 | 
					        year = int(year)
 | 
				
			||||||
        month = int(month)
 | 
					        month = int(month)
 | 
				
			||||||
        return cl.render(*args, year = year, month = month, **kwargs)
 | 
					        return cl.render(*args, year = year, month = month, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render_exp._exposure.name = 'render'
 | 
					    render_exp._exposure.name = 'render'
 | 
				
			||||||
    render_exp._exposure.pattern = '(?P<year>[0-9]{4})/(?P<month>[0-1]?[0-9])'
 | 
					    render_exp._exposure.pattern = '(?P<year>[0-9]{4})/(?P<month>[0-1]?[0-9])'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@ var Actions = {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Init an existing action HTML element
 | 
					    /// Init an existing action HTML element
 | 
				
			||||||
    init_action: function(item, action_id, data, url) {
 | 
					    init_action: function(item, action_id, data, url) {
 | 
				
			||||||
        var action = this.registry[action_id];
 | 
					        var action = this.registry[action_id];
 | 
				
			||||||
@ -54,15 +53,15 @@ var Actions = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        action = Actions.registry[action];
 | 
					        action = Actions.registry[action];
 | 
				
			||||||
        if(!action)
 | 
					        if(!action)
 | 
				
			||||||
            return
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = item.data || item.dataset;
 | 
					        action.handler(item.data || item.dataset, item);
 | 
				
			||||||
        action.handler(data, item);
 | 
					 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
document.addEventListener('DOMContentLoaded', function(event) {
 | 
					document.addEventListener('DOMContentLoaded', function(event) {
 | 
				
			||||||
    var items = document.querySelectorAll('.action[action]');
 | 
					    var items = document.querySelectorAll('.action[action]');
 | 
				
			||||||
    for(var i = 0; i < items.length; i++) {
 | 
					    for(var i = 0; i < items.length; i++) {
 | 
				
			||||||
@ -72,6 +71,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
 | 
				
			|||||||
        Actions.init_action(item, action_id, data);
 | 
					        Actions.init_action(item, action_id, data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}, false);
 | 
					}, false);
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Small utility used to make XMLHttpRequests, and map results on objects.
 | 
					/// Small utility used to make XMLHttpRequests, and map results on objects.
 | 
				
			||||||
 | 
				
			|||||||
@ -60,10 +60,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    {% if object.actions and 'actions' in list.fields %}
 | 
					    {% if object.actions and 'actions' in list.fields %}
 | 
				
			||||||
        <div class="actions">
 | 
					        <div class="actions">
 | 
				
			||||||
        {% for action_id,action_data in object.actions.items %}
 | 
					            {% for action in object.actions %}
 | 
				
			||||||
            <a class="action" action="{{action_id}}"
 | 
					            {{ action|safe }}
 | 
				
			||||||
            {% for k,v in action_data.items %}data-{{ k }}="{{ v }}"{% endfor %}></a>
 | 
					            {% endfor %}
 | 
				
			||||||
        {% endfor %}
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,14 @@
 | 
				
			|||||||
        <link rel="stylesheet" href="{% static website.styles %}" type="text/css">
 | 
					        <link rel="stylesheet" href="{% static website.styles %}" type="text/css">
 | 
				
			||||||
        {% endif %}
 | 
					        {% endif %}
 | 
				
			||||||
        <script src="{% static "aircox/cms/scripts.js" %}"></script>
 | 
					        <script src="{% static "aircox/cms/scripts.js" %}"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {% if actions %}
 | 
				
			||||||
 | 
					        <script>
 | 
				
			||||||
 | 
					        {{ actions|safe }}
 | 
				
			||||||
 | 
					        </script>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <title>{% if title %}{{ title|striptags }} - {% endif %}{{ website.name }}</title>
 | 
					        <title>{% if title %}{{ title|striptags }} - {% endif %}{{ website.name }}</title>
 | 
				
			||||||
    </head>
 | 
					    </head>
 | 
				
			||||||
    <body>
 | 
					    <body>
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ from django import template
 | 
				
			|||||||
from django.core.urlresolvers import reverse
 | 
					from django.core.urlresolvers import reverse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import aircox.cms.utils as utils
 | 
					import aircox.cms.utils as utils
 | 
				
			||||||
 | 
					import aircox.cms.actions as actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
register = template.Library()
 | 
					register = template.Library()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,8 +41,12 @@ def threads(post, sep = '/'):
 | 
				
			|||||||
        for post in posts[:-1] if post.published
 | 
					        for post in posts[:-1] if post.published
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register.filter(name='around')
 | 
					@register.filter(name='around')
 | 
				
			||||||
def around(page_num, n):
 | 
					def around(page_num, n):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return a range of value around a given number.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    return range(page_num-n, page_num+n+1)
 | 
					    return range(page_num-n, page_num+n+1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								cms/views.py
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cms/views.py
									
									
									
									
									
								
							@ -6,8 +6,9 @@ from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			|||||||
from django.contrib import messages
 | 
					from django.contrib import messages
 | 
				
			||||||
from django.http import Http404
 | 
					from django.http import Http404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox.cms.actions import Actions
 | 
				
			||||||
import aircox.cms.sections as sections
 | 
					import aircox.cms.sections as sections
 | 
				
			||||||
import aircox.cms.sections as sections_
 | 
					sections_ = sections # used for name clashes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseView:
 | 
					class BaseView:
 | 
				
			||||||
@ -101,6 +102,7 @@ class BaseView:
 | 
				
			|||||||
                    for k, v in self.menus.items()
 | 
					                    for k, v in self.menus.items()
 | 
				
			||||||
                    if v is not self
 | 
					                    if v is not self
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            context['actions'] = Actions.register_code()
 | 
				
			||||||
            context['embed'] = False
 | 
					            context['embed'] = False
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            context['embed'] = True
 | 
					            context['embed'] = True
 | 
				
			||||||
@ -163,7 +165,7 @@ class PostListView(BaseView, ListView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return qs
 | 
					        return qs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def init_list(self):
 | 
					    def prepare_list(self):
 | 
				
			||||||
        if not self.list:
 | 
					        if not self.list:
 | 
				
			||||||
           self.list = sections.List(
 | 
					           self.list = sections.List(
 | 
				
			||||||
               truncate = 32,
 | 
					               truncate = 32,
 | 
				
			||||||
@ -180,8 +182,11 @@ class PostListView(BaseView, ListView):
 | 
				
			|||||||
                if field in self.list.fields
 | 
					                if field in self.list.fields
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # done in list
 | 
				
			||||||
 | 
					        # Actions.make(self.request, object_list = self.object_list)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
        self.init_list()
 | 
					        self.prepare_list()
 | 
				
			||||||
        self.add_css_class('list')
 | 
					        self.add_css_class('list')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        context = super().get_context_data(**kwargs)
 | 
					        context = super().get_context_data(**kwargs)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								notes.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								notes.md
									
									
									
									
									
								
							@ -18,34 +18,33 @@
 | 
				
			|||||||
        - config generation and sound diffusion
 | 
					        - config generation and sound diffusion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- cms:
 | 
					- cms:
 | 
				
			||||||
    - empty content -> empty string
 | 
					    - empty content/list -> nothing
 | 
				
			||||||
    - update documentation:
 | 
					    - update documentation:
 | 
				
			||||||
        - cms.script
 | 
					        - cms.script
 | 
				
			||||||
        - cms.exposure; make it right, see nomenclature, + docstring
 | 
					        - cms.exposure; make it right, see nomenclature, + docstring
 | 
				
			||||||
 | 
					        - cms.actions;
 | 
				
			||||||
    - admin cms
 | 
					    - admin cms
 | 
				
			||||||
    - sections:
 | 
					 | 
				
			||||||
        - article list with the focus
 | 
					 | 
				
			||||||
            -> set html attribute based on values that are public
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
- website:
 | 
					- website:
 | 
				
			||||||
    - render schedule does not get the correct list
 | 
					    - render schedule does not get the correct list
 | 
				
			||||||
        -> postlistview has not the same queryset as website/sections/schedule
 | 
					        -> postlistview has not the same queryset as website/sections/schedule
 | 
				
			||||||
    - diffusions:
 | 
					    - diffusions:
 | 
				
			||||||
        - filter sounds for undiffused diffusions
 | 
					        - print program's name in lists / clean up that thing also a bit
 | 
				
			||||||
        - print sounds of diffusions
 | 
					    - article list with the focus
 | 
				
			||||||
        - print program's name in lists
 | 
					 | 
				
			||||||
    - player:
 | 
					    - player:
 | 
				
			||||||
        - mixcloud
 | 
					        - mixcloud
 | 
				
			||||||
        - seek bar
 | 
					        - seek bar + timer
 | 
				
			||||||
 | 
					        - remove from playing playlist -> stop
 | 
				
			||||||
    - list of played diffusions and tracks when non-stop;
 | 
					    - list of played diffusions and tracks when non-stop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Later todo
 | 
					# Long term TODO
 | 
				
			||||||
- sounds monitor: max_size of path, take in account
 | 
					- sounds monitor: max_size of path, take in account
 | 
				
			||||||
- logs: archive functionnality
 | 
					- logs: archive functionnality
 | 
				
			||||||
- track stats for diffusions
 | 
					- track stats for diffusions
 | 
				
			||||||
- debug/prod configuration
 | 
					- debug/prod configuration
 | 
				
			||||||
- player support diffusions with multiple archive files
 | 
					- player support diffusions with multiple archive files
 | 
				
			||||||
- view as grid
 | 
					- view as grid
 | 
				
			||||||
 | 
					- actions -> noscript case, think of accessibility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -56,10 +56,11 @@ class NameableAdmin(admin.ModelAdmin):
 | 
				
			|||||||
@admin.register(Sound)
 | 
					@admin.register(Sound)
 | 
				
			||||||
class SoundAdmin(NameableAdmin):
 | 
					class SoundAdmin(NameableAdmin):
 | 
				
			||||||
    fields = None
 | 
					    fields = None
 | 
				
			||||||
    list_display = ['id', 'name', 'duration', 'type', 'mtime', 'good_quality', 'removed']
 | 
					    list_display = ['id', 'name', 'duration', 'type', 'mtime',
 | 
				
			||||||
 | 
					                    'public', 'good_quality', 'removed']
 | 
				
			||||||
    fieldsets = [
 | 
					    fieldsets = [
 | 
				
			||||||
        (None, { 'fields': NameableAdmin.fields + ['path', 'type', 'diffusion'] } ),
 | 
					        (None, { 'fields': NameableAdmin.fields + ['path', 'type', 'diffusion'] } ),
 | 
				
			||||||
        (None, { 'fields': ['embed', 'duration', 'mtime'] }),
 | 
					        (None, { 'fields': ['embed', 'duration', 'public', 'mtime'] }),
 | 
				
			||||||
        (None, { 'fields': ['removed', 'good_quality' ] } )
 | 
					        (None, { 'fields': ['removed', 'good_quality' ] } )
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    readonly_fields = ('path', 'duration',)
 | 
					    readonly_fields = ('path', 'duration',)
 | 
				
			||||||
 | 
				
			|||||||
@ -154,6 +154,11 @@ class Sound(Nameable):
 | 
				
			|||||||
        default = False,
 | 
					        default = False,
 | 
				
			||||||
        help_text = _('sound\'s quality is okay')
 | 
					        help_text = _('sound\'s quality is okay')
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    public = models.BooleanField(
 | 
				
			||||||
 | 
					        _('public'),
 | 
				
			||||||
 | 
					        default = False,
 | 
				
			||||||
 | 
					        help_text = _('the sound is accessible to the public')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_mtime(self):
 | 
					    def get_mtime(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -204,14 +209,40 @@ class Sound(Nameable):
 | 
				
			|||||||
            return True
 | 
					            return True
 | 
				
			||||||
        return old_removed != self.removed
 | 
					        return old_removed != self.removed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save(self, check = True, *args, **kwargs):
 | 
					    def check_perms(self):
 | 
				
			||||||
        if check:
 | 
					        """
 | 
				
			||||||
            self.check_on_file()
 | 
					        Check permissions and update them if this is activated
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not settings.AIRCOX_SOUND_AUTO_CHMOD or \
 | 
				
			||||||
 | 
					                self.removed or not os.path.exists(self.path):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        flags = settings.AIRCOX_SOUND_CHMOD_FLAGS[self.public]
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            os.chmod(self.path, flags)
 | 
				
			||||||
 | 
					        except PermissionError as err:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                'cannot set permissions {} to file {}: {}'.format(
 | 
				
			||||||
 | 
					                    self.flags[self.public],
 | 
				
			||||||
 | 
					                    self.path, err
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __check_name(self):
 | 
				
			||||||
        if not self.name and self.path:
 | 
					        if not self.name and self.path:
 | 
				
			||||||
 | 
					            # FIXME: later, remove date?
 | 
				
			||||||
            self.name = os.path.basename(self.path)
 | 
					            self.name = os.path.basename(self.path)
 | 
				
			||||||
            self.name = os.path.splitext(self.name)[0]
 | 
					            self.name = os.path.splitext(self.name)[0]
 | 
				
			||||||
            self.name = self.name.replace('_', ' ')
 | 
					            self.name = self.name.replace('_', ' ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self.__check_name()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self, check = True, *args, **kwargs):
 | 
				
			||||||
 | 
					        if check:
 | 
				
			||||||
 | 
					            self.check_on_file()
 | 
				
			||||||
 | 
					        self.__check_name()
 | 
				
			||||||
        super().save(*args, **kwargs)
 | 
					        super().save(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import stat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -18,6 +19,15 @@ ensure('AIRCOX_SOUND_ARCHIVES_SUBDIR', 'archives')
 | 
				
			|||||||
# Sub directory used for the excerpts of the episode
 | 
					# Sub directory used for the excerpts of the episode
 | 
				
			||||||
ensure('AIRCOX_SOUND_EXCERPTS_SUBDIR', 'excerpts')
 | 
					ensure('AIRCOX_SOUND_EXCERPTS_SUBDIR', 'excerpts')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Change sound perms based on 'public' attribute if True
 | 
				
			||||||
 | 
					ensure('AIRCOX_SOUND_AUTO_CHMOD', True)
 | 
				
			||||||
 | 
					# Chmod bits flags as a tuple for (not public, public). Use os.chmod
 | 
				
			||||||
 | 
					# and stat.*
 | 
				
			||||||
 | 
					ensure(
 | 
				
			||||||
 | 
					    'AIRCOX_SOUND_CHMOD_FLAGS',
 | 
				
			||||||
 | 
					    (stat.S_IRWXU, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH )
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Quality attributes passed to sound_quality_check from sounds_monitor
 | 
					# Quality attributes passed to sound_quality_check from sounds_monitor
 | 
				
			||||||
ensure('AIRCOX_SOUND_QUALITY', {
 | 
					ensure('AIRCOX_SOUND_QUALITY', {
 | 
				
			||||||
        'attribute': 'RMS lev dB',
 | 
					        'attribute': 'RMS lev dB',
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										75
									
								
								website/actions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								website/actions.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					from django.utils import timezone as tz
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox.cms.actions import Action
 | 
				
			||||||
 | 
					import aircox.website.utils as utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddToPlaylist(Action):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Remember a sound and add it into the default playlist. The given
 | 
				
			||||||
 | 
					    object can be:
 | 
				
			||||||
 | 
					    - a Diffusion post
 | 
				
			||||||
 | 
					    - a programs.Sound instance
 | 
				
			||||||
 | 
					    - an object with an attribute 'sound' used to generate the code
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    id = 'sound.add'
 | 
				
			||||||
 | 
					    symbol = '☰'
 | 
				
			||||||
 | 
					    title = _('add to the playlist')
 | 
				
			||||||
 | 
					    code = """
 | 
				
			||||||
 | 
					    function(sound, item) {
 | 
				
			||||||
 | 
					        Player.playlist.add(sound);
 | 
				
			||||||
 | 
					        item.parentNode.removeChild(item)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def make_for_diffusions(cl, request, object):
 | 
				
			||||||
 | 
					        from aircox.website.sections import Player
 | 
				
			||||||
 | 
					        if object.related.end > tz.make_aware(tz.datetime.now()):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        archives = object.related.get_archives()
 | 
				
			||||||
 | 
					        if not archives:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sound = Player.make_sound(object, archives[0])
 | 
				
			||||||
 | 
					        return cl.to_str(object, **sound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def make_for_sound(cl, request, object):
 | 
				
			||||||
 | 
					        from aircox.website.sections import Player
 | 
				
			||||||
 | 
					        sound = Player.make_sound(None, object)
 | 
				
			||||||
 | 
					        return cl.to_str(object, **sound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def test(cl, request, object, in_list):
 | 
				
			||||||
 | 
					        from aircox.programs.models import Sound
 | 
				
			||||||
 | 
					        from aircox.website.models import Diffusion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(object)
 | 
				
			||||||
 | 
					        if not in_list:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if issubclass(type(object), Diffusion):
 | 
				
			||||||
 | 
					            return cl.make_for_diffusions(request, object)
 | 
				
			||||||
 | 
					        if issubclass(type(object), Sound):
 | 
				
			||||||
 | 
					            return cl.make_for_sound(request, object)
 | 
				
			||||||
 | 
					        if hasattr(object, 'sound') and object.sound:
 | 
				
			||||||
 | 
					            return cl.make_for_sound(request, object.sound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Play(AddToPlaylist):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Play a sound
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    id = 'sound.play'
 | 
				
			||||||
 | 
					    symbol = '▶'
 | 
				
			||||||
 | 
					    title = _('listen')
 | 
				
			||||||
 | 
					    code = """
 | 
				
			||||||
 | 
					    function(sound) {
 | 
				
			||||||
 | 
					        sound = Player.playlist.add(sound);
 | 
				
			||||||
 | 
					        Player.select_playlist(Player.playlist);
 | 
				
			||||||
 | 
					        Player.select(sound, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -21,5 +21,4 @@ admin.site.register(models.Diffusion, cms.RelatedPostAdmin)
 | 
				
			|||||||
cms.inject_inline(programs.Diffusion, TrackInline, True)
 | 
					cms.inject_inline(programs.Diffusion, TrackInline, True)
 | 
				
			||||||
cms.inject_related_inline(models.Program, True)
 | 
					cms.inject_related_inline(models.Program, True)
 | 
				
			||||||
cms.inject_related_inline(models.Diffusion, True)
 | 
					cms.inject_related_inline(models.Diffusion, True)
 | 
				
			||||||
cms.inject_related_inline(models.Sound, True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import aircox.programs.models as programs
 | 
					import aircox.programs.models as programs
 | 
				
			||||||
import aircox.cms.models as cms
 | 
					import aircox.cms.models as cms
 | 
				
			||||||
 | 
					import aircox.website.actions as actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Article (cms.Post):
 | 
					class Article (cms.Post):
 | 
				
			||||||
@ -50,6 +51,8 @@ class Program (cms.RelatedPost):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Diffusion (cms.RelatedPost):
 | 
					class Diffusion (cms.RelatedPost):
 | 
				
			||||||
 | 
					    actions = [actions.Play, actions.AddToPlaylist]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Relation:
 | 
					    class Relation:
 | 
				
			||||||
        model = programs.Diffusion
 | 
					        model = programs.Diffusion
 | 
				
			||||||
        bindings = {
 | 
					        bindings = {
 | 
				
			||||||
@ -78,53 +81,3 @@ class Diffusion (cms.RelatedPost):
 | 
				
			|||||||
            'day': self.related.initial.start.strftime('%A %d/%m')
 | 
					            'day': self.related.initial.start.strftime('%A %d/%m')
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class Sound (cms.RelatedPost):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Publication concerning sound. In order to manage access of sound
 | 
					 | 
				
			||||||
    files in the filesystem, we use permissions -- it is up to the
 | 
					 | 
				
			||||||
    user to work select the correct groups and permissions.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    embed = models.TextField(
 | 
					 | 
				
			||||||
        _('embedding code'),
 | 
					 | 
				
			||||||
        blank=True, null=True,
 | 
					 | 
				
			||||||
        help_text = _('HTML code used to embed a sound from an external '
 | 
					 | 
				
			||||||
                      'plateform'),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Embedding code if the file has been published on an external
 | 
					 | 
				
			||||||
    plateform.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto_chmod = True
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    change file permission depending on the "published" attribute.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    chmod_flags = (stat.S_IRWXU, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH )
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    chmod bit flags, for (not_published, published)
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    class Relation:
 | 
					 | 
				
			||||||
        model = programs.Sound
 | 
					 | 
				
			||||||
        bindings = {
 | 
					 | 
				
			||||||
            'title': 'name',
 | 
					 | 
				
			||||||
            'date': 'mtime',
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        rel_to_post = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def save(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        super().save(*args, **kwargs)
 | 
					 | 
				
			||||||
        if self.auto_chmod and not self.related.removed and \
 | 
					 | 
				
			||||||
                os.path.exists(self.related.path):
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                os.chmod(self.related.path,
 | 
					 | 
				
			||||||
                         self.chmod_flags[self.published])
 | 
					 | 
				
			||||||
            except PermissionError as err:
 | 
					 | 
				
			||||||
                logger.error(
 | 
					 | 
				
			||||||
                    'cannot set permission {} to file {}: {}'.format(
 | 
					 | 
				
			||||||
                        self.chmod_flags[self.published],
 | 
					 | 
				
			||||||
                        self.related.path, err
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -7,12 +7,16 @@ import aircox.programs.models as programs
 | 
				
			|||||||
import aircox.cms.models as cms
 | 
					import aircox.cms.models as cms
 | 
				
			||||||
import aircox.cms.routes as routes
 | 
					import aircox.cms.routes as routes
 | 
				
			||||||
import aircox.cms.sections as sections
 | 
					import aircox.cms.sections as sections
 | 
				
			||||||
import aircox.cms.decorators as decorators
 | 
					
 | 
				
			||||||
 | 
					from aircox.cms.exposures import expose
 | 
				
			||||||
 | 
					from aircox.cms.actions import Action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import aircox.website.models as models
 | 
					import aircox.website.models as models
 | 
				
			||||||
 | 
					import aircox.website.actions as actions
 | 
				
			||||||
 | 
					import aircox.website.utils as utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@decorators.expose
 | 
					@expose
 | 
				
			||||||
class Player(sections.Section):
 | 
					class Player(sections.Section):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Display a player that is cool.
 | 
					    Display a player that is cool.
 | 
				
			||||||
@ -24,7 +28,7 @@ class Player(sections.Section):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    #default_sounds
 | 
					    #default_sounds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @decorators.expose
 | 
					    @expose
 | 
				
			||||||
    def on_air(cl, request):
 | 
					    def on_air(cl, request):
 | 
				
			||||||
        qs = programs.Diffusion.get(
 | 
					        qs = programs.Diffusion.get(
 | 
				
			||||||
            now = True,
 | 
					            now = True,
 | 
				
			||||||
@ -46,17 +50,56 @@ class Player(sections.Section):
 | 
				
			|||||||
            'item': post,
 | 
					            'item': post,
 | 
				
			||||||
            'list': sections.List,
 | 
					            'list': sections.List,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    on_air._exposure.template_name = 'aircox/cms/list_item.html'
 | 
					    on_air._exposure.template_name = 'aircox/cms/list_item.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def make_sound(post = None, sound = None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return a standard item from a sound that can be used as a
 | 
				
			||||||
 | 
					        player's item
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        r = {
 | 
				
			||||||
 | 
					            'title': post.title if post else sound.name,
 | 
				
			||||||
 | 
					            'url': post.url() if post else None,
 | 
				
			||||||
 | 
					            'info': utils.duration_to_str(sound.duration),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if sound.embed:
 | 
				
			||||||
 | 
					            r['embed'] = sound.embed
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            r['stream'] = sound.url()
 | 
				
			||||||
 | 
					        return r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_recents(cl, count):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return a list of count recent published diffusions that have sounds,
 | 
				
			||||||
 | 
					        as item usable in the playlist.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        qs = models.Diffusion.objects \
 | 
				
			||||||
 | 
					                             .filter(published = True) \
 | 
				
			||||||
 | 
					                             .filter(related__end__lte = tz.datetime.now()) \
 | 
				
			||||||
 | 
					                             .order_by('-related__end')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        recents = []
 | 
				
			||||||
 | 
					        for post in qs:
 | 
				
			||||||
 | 
					            archives = post.related.get_archives()
 | 
				
			||||||
 | 
					            if not archives:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            archives = archives[0]
 | 
				
			||||||
 | 
					            recents.append(cl.make_sound(post, archives))
 | 
				
			||||||
 | 
					            if len(recents) >= count:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					        return recents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, *args, **kwargs):
 | 
					    def get_context_data(self, *args, **kwargs):
 | 
				
			||||||
        context = super().get_context_data(*args, **kwargs)
 | 
					        context = super().get_context_data(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        context.update({
 | 
					        context.update({
 | 
				
			||||||
            'base_template': 'aircox/cms/section.html',
 | 
					            'base_template': 'aircox/cms/section.html',
 | 
				
			||||||
            'live_streams': self.live_streams,
 | 
					            'live_streams': self.live_streams,
 | 
				
			||||||
            'last_sounds': models.Sound.objects. \
 | 
					            'recents': self.get_recents(10),
 | 
				
			||||||
                                filter(published = True). \
 | 
					 | 
				
			||||||
                                order_by('-pk')[:10],
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -99,7 +142,7 @@ class Diffusions(sections.List):
 | 
				
			|||||||
        #                .order_by('-start')[:self.prev_count])
 | 
					        #                .order_by('-start')[:self.prev_count])
 | 
				
			||||||
        #return r
 | 
					        #return r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def prepare_object_list(self, object_list):
 | 
					    def prepare_list(self, object_list):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        This function just prepare the list of object, in order to:
 | 
					        This function just prepare the list of object, in order to:
 | 
				
			||||||
        - have a good title
 | 
					        - have a good title
 | 
				
			||||||
@ -115,17 +158,6 @@ class Diffusions(sections.List):
 | 
				
			|||||||
                post.title = ': ' + post.title if post.title else \
 | 
					                post.title = ': ' + post.title if post.title else \
 | 
				
			||||||
                            ' // ' + post.related.start.strftime('%A %d %B')
 | 
					                            ' // ' + post.related.start.strftime('%A %d %B')
 | 
				
			||||||
                post.title = name + post.title
 | 
					                post.title = name + post.title
 | 
				
			||||||
 | 
					 | 
				
			||||||
            # sounds
 | 
					 | 
				
			||||||
            pl = post.related.get_archives()
 | 
					 | 
				
			||||||
            if pl:
 | 
					 | 
				
			||||||
                item = { 'title': post.title, 'stream': pl[0].url,
 | 
					 | 
				
			||||||
                         'url': post.url() }
 | 
					 | 
				
			||||||
                post.actions = {
 | 
					 | 
				
			||||||
                    'sound.play': item,
 | 
					 | 
				
			||||||
                    'sound.mark': item,
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return object_list
 | 
					        return object_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_object_list(self):
 | 
					    def get_object_list(self):
 | 
				
			||||||
@ -205,7 +237,24 @@ class Playlist(sections.List):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Sounds(sections.List):
 | 
					class Sounds(sections.List):
 | 
				
			||||||
    pass
 | 
					    title = _('Podcasts')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_object_list(self):
 | 
				
			||||||
 | 
					        if self.object.related.end > tz.make_aware(tz.datetime.now()):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sounds = programs.Sound.objects.filter(
 | 
				
			||||||
 | 
					            diffusion = self.object.related
 | 
				
			||||||
 | 
					            # public = True
 | 
				
			||||||
 | 
					        ).order_by('type')
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            sections.ListItem(
 | 
				
			||||||
 | 
					                title=sound.name,
 | 
				
			||||||
 | 
					                info=utils.duration_to_str(sound.duration),
 | 
				
			||||||
 | 
					                sound = sound,
 | 
				
			||||||
 | 
					                actions = [ actions.AddToPlaylist, actions.Play ],
 | 
				
			||||||
 | 
					            ) for sound in sounds
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Schedule(Diffusions):
 | 
					class Schedule(Diffusions):
 | 
				
			||||||
 | 
				
			|||||||
@ -102,7 +102,7 @@
 | 
				
			|||||||
    .playlist .actions label,
 | 
					    .playlist .actions label,
 | 
				
			||||||
    #playlist-live .actions,
 | 
					    #playlist-live .actions,
 | 
				
			||||||
    #playlist-recents .actions a.action[action="remove"],
 | 
					    #playlist-recents .actions a.action[action="remove"],
 | 
				
			||||||
    #playlist-marked .actions a.action[action="sound.mark"],
 | 
					    #playlist-favorites .actions a.action[action="sound.mark"],
 | 
				
			||||||
    .playlist .actions a.action[action="sound.play"],
 | 
					    .playlist .actions a.action[action="sound.play"],
 | 
				
			||||||
    .playlist .actions a.url:not([href]),
 | 
					    .playlist .actions a.url:not([href]),
 | 
				
			||||||
    .playlist .actions a.url[href=""] {
 | 
					    .playlist .actions a.url[href=""] {
 | 
				
			||||||
@ -118,8 +118,11 @@
 | 
				
			|||||||
        <h2 class="title"></h2>
 | 
					        <h2 class="title"></h2>
 | 
				
			||||||
        <div class="info"></div>
 | 
					        <div class="info"></div>
 | 
				
			||||||
        <div class="actions">
 | 
					        <div class="actions">
 | 
				
			||||||
 | 
					            <a class="action" action="sound.mark"
 | 
				
			||||||
 | 
					               title="{% trans "add to my favorites" %}">★</a>
 | 
				
			||||||
            <a class="url action" title="{% trans "more informations" %}">➔</a>
 | 
					            <a class="url action" title="{% trans "more informations" %}">➔</a>
 | 
				
			||||||
            <a class="action" action="remove" title="{% trans "remove from the playlist" %}">✖</a>
 | 
					            <a class="action" action="sound.remove"
 | 
				
			||||||
 | 
					               title="{% trans "remove from the playlist" %}">✖</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    <div class="player-box">
 | 
					    <div class="player-box">
 | 
				
			||||||
@ -130,7 +133,7 @@
 | 
				
			|||||||
                Your browser does not support the <code>audio</code> element.
 | 
					                Your browser does not support the <code>audio</code> element.
 | 
				
			||||||
            </audio>
 | 
					            </audio>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <span class="player-button" onclick="player.play()"
 | 
					            <span class="player-button" onclick="Player.play()"
 | 
				
			||||||
              title="{% trans "play/pause" %}"></span>
 | 
					              title="{% trans "play/pause" %}"></span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <h3 class="title"></h3>
 | 
					            <h3 class="title"></h3>
 | 
				
			||||||
@ -149,7 +152,7 @@
 | 
				
			|||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
playerStore = {
 | 
					PlayerStore = {
 | 
				
			||||||
    // save data to localstorage, or remove it if data is null
 | 
					    // save data to localstorage, or remove it if data is null
 | 
				
			||||||
    set: function(name, data) {
 | 
					    set: function(name, data) {
 | 
				
			||||||
        name = 'player.' + name;
 | 
					        name = 'player.' + name;
 | 
				
			||||||
@ -194,8 +197,7 @@ playerStore = {
 | 
				
			|||||||
// * tab: text to put in the tab
 | 
					// * tab: text to put in the tab
 | 
				
			||||||
// * items: list of items to append
 | 
					// * items: list of items to append
 | 
				
			||||||
// * store: store the playlist in localStorage
 | 
					// * store: store the playlist in localStorage
 | 
				
			||||||
function Playlist(player, name, tab, items, store = false) {
 | 
					function Playlist(name, tab, items, store = false) {
 | 
				
			||||||
    this.player = player;
 | 
					 | 
				
			||||||
    this.name = name;
 | 
					    this.name = name;
 | 
				
			||||||
    this.store = store;
 | 
					    this.store = store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -206,14 +208,14 @@ function Playlist(player, name, tab, items, store = false) {
 | 
				
			|||||||
    var self = this;
 | 
					    var self = this;
 | 
				
			||||||
    this.tab = document.createElement('a');
 | 
					    this.tab = document.createElement('a');
 | 
				
			||||||
    this.tab.addEventListener('click', function(event) {
 | 
					    this.tab.addEventListener('click', function(event) {
 | 
				
			||||||
        player.select_playlist(self);
 | 
					        Player.select_playlist(self);
 | 
				
			||||||
        event.preventDefault();
 | 
					        event.preventDefault();
 | 
				
			||||||
    }, true);
 | 
					    }, true);
 | 
				
			||||||
    this.tab.className = 'tab';
 | 
					    this.tab.className = 'tab';
 | 
				
			||||||
    this.tab.innerHTML = tab;
 | 
					    this.tab.innerHTML = tab;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    player.playlists.appendChild(this.playlist);
 | 
					    Player.playlists.appendChild(this.playlist);
 | 
				
			||||||
    player.playlists.querySelector('nav').appendChild(this.tab);
 | 
					    Player.playlists.querySelector('nav').appendChild(this.tab);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.items = [];
 | 
					    this.items = [];
 | 
				
			||||||
    if(store)
 | 
					    if(store)
 | 
				
			||||||
@ -237,15 +239,30 @@ Playlist.prototype = {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// add an item to the playlist or container, if not in this playlist.
 | 
					    /// add sound actions to a given element
 | 
				
			||||||
 | 
					    add_actions: function(item, container) {
 | 
				
			||||||
 | 
					        Actions.add_action(container, 'sound.mark', item);
 | 
				
			||||||
 | 
					        Actions.add_action(container, 'sound.play', item, item.stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elm = container.querySelector('.actions a[action="sound.mark"]');
 | 
				
			||||||
 | 
					        elm.addEventListener('click', function(event) {
 | 
				
			||||||
 | 
					            Player.favorites.add(item);
 | 
				
			||||||
 | 
					        }, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var elm = container.querySelector('.actions a[action="sound.remove"]');
 | 
				
			||||||
 | 
					        elm.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            item.playlist.remove(item);
 | 
				
			||||||
 | 
					        }, true);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// add an item to the playlist or container, if not in this.playlist.
 | 
				
			||||||
    /// return the existing item or the newly created item.
 | 
					    /// return the existing item or the newly created item.
 | 
				
			||||||
    add: function(item, container) {
 | 
					    add: function(item, container) {
 | 
				
			||||||
        var item_ = this.find(item);
 | 
					        var item_ = this.find(item);
 | 
				
			||||||
        if(item_)
 | 
					        if(item_)
 | 
				
			||||||
            return item_;
 | 
					            return item_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var player = this.player;
 | 
					        var elm = Player.player.querySelector('.item').cloneNode(true);
 | 
				
			||||||
        var elm = player.player.querySelector('.item').cloneNode(true);
 | 
					 | 
				
			||||||
        elm.removeAttribute('style');
 | 
					        elm.removeAttribute('style');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(!container)
 | 
					        if(!container)
 | 
				
			||||||
@ -255,10 +272,18 @@ Playlist.prototype = {
 | 
				
			|||||||
        else
 | 
					        else
 | 
				
			||||||
            container.appendChild(elm);
 | 
					            container.appendChild(elm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        item.elm      = elm;
 | 
					        item = {
 | 
				
			||||||
        item.playlist = this;
 | 
					            title: item.title,
 | 
				
			||||||
        elm.item      = item;
 | 
					            url: item.url,
 | 
				
			||||||
 | 
					            stream: item.stream,
 | 
				
			||||||
 | 
					            info: item.info,
 | 
				
			||||||
 | 
					            seekable: 'seekable' in item ? item.seekable : true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            elm: elm,
 | 
				
			||||||
 | 
					            playlist: this,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elm.item      = item;
 | 
				
			||||||
        elm.querySelector('.title').innerHTML = item.title || '';
 | 
					        elm.querySelector('.title').innerHTML = item.title || '';
 | 
				
			||||||
        elm.querySelector('.url').href = item.url || '';
 | 
					        elm.querySelector('.url').href = item.url || '';
 | 
				
			||||||
        elm.querySelector('.info').innerHTML = item.info || '';
 | 
					        elm.querySelector('.info').innerHTML = item.info || '';
 | 
				
			||||||
@ -273,13 +298,13 @@ Playlist.prototype = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var item = event.currentTarget.item;
 | 
					            var item = event.currentTarget.item;
 | 
				
			||||||
            if(item.stream || item.embed)
 | 
					            if(item.stream || item.embed)
 | 
				
			||||||
              player.select(item);
 | 
					                Player.select(item);
 | 
				
			||||||
            event.stopPropagation();
 | 
					            event.stopPropagation();
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }, false);
 | 
					        }, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(item.embed || item.stream)
 | 
					        if(item.embed || item.stream)
 | 
				
			||||||
            player.add_actions(item, elm);
 | 
					            this.add_actions(item, elm);
 | 
				
			||||||
        this.items.push(item);
 | 
					        this.items.push(item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(container == this.playlist && this.store)
 | 
					        if(container == this.playlist && this.store)
 | 
				
			||||||
@ -322,32 +347,32 @@ Playlist.prototype = {
 | 
				
			|||||||
            delete item.playlist;
 | 
					            delete item.playlist;
 | 
				
			||||||
            pl.push(item);
 | 
					            pl.push(item);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        playerStore.set('playlist.' + this.name, pl)
 | 
					        PlayerStore.set('playlist.' + this.name, pl)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Load playlist from local storage
 | 
					    /// Load playlist from local storage
 | 
				
			||||||
    load: function() {
 | 
					    load: function() {
 | 
				
			||||||
      var pl = playerStore.get('playlist.' + this.name);
 | 
					      var pl = PlayerStore.get('playlist.' + this.name);
 | 
				
			||||||
      if(pl)
 | 
					      if(pl)
 | 
				
			||||||
        this.add_list(pl);
 | 
					        this.add_list(pl);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// called by the player when the given item is unselected
 | 
					    /// called by Player when the given item is unselected
 | 
				
			||||||
    unselect: function(player, item) {
 | 
					    unselect: function(item) {
 | 
				
			||||||
        this.tab.removeAttribute('active');
 | 
					        this.tab.removeAttribute('active');
 | 
				
			||||||
        if(item.elm)
 | 
					        if(item.elm)
 | 
				
			||||||
          item.elm.removeAttribute('selected');
 | 
					          item.elm.removeAttribute('selected');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var audio = this.player.audio;
 | 
					        var audio = Player.audio;
 | 
				
			||||||
        if(this.store && !audio.ended) {
 | 
					        if(this.store && !audio.ended) {
 | 
				
			||||||
            item.currentTime = audio.currentTime;
 | 
					            item.currentTime = audio.currentTime;
 | 
				
			||||||
            this.save();
 | 
					            this.save();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// called by the player when the given item is selected, in order to
 | 
					    /// called by Player when the given item is selected, in order to
 | 
				
			||||||
    /// prepare it.
 | 
					    /// prepare it.
 | 
				
			||||||
    select: function(player, item) {
 | 
					    select: function(item) {
 | 
				
			||||||
        this.tab.setAttribute('active', 'true');
 | 
					        this.tab.setAttribute('active', 'true');
 | 
				
			||||||
        if(item.elm)
 | 
					        if(item.elm)
 | 
				
			||||||
            item.elm.setAttribute('selected', 'true');
 | 
					            item.elm.setAttribute('selected', 'true');
 | 
				
			||||||
@ -355,15 +380,15 @@ Playlist.prototype = {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
player = {
 | 
					Player = {
 | 
				
			||||||
    /// main container of the player
 | 
					    /// main container of the Player
 | 
				
			||||||
    player: undefined,
 | 
					    player: undefined,
 | 
				
			||||||
    /// <audio> container
 | 
					    /// <audio> container
 | 
				
			||||||
    audio: undefined,
 | 
					    audio: undefined,
 | 
				
			||||||
    /// controls
 | 
					    /// controls
 | 
				
			||||||
    controls: undefined,
 | 
					    controls: undefined,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// init player
 | 
					    /// init Player
 | 
				
			||||||
    init: function(id) {
 | 
					    init: function(id) {
 | 
				
			||||||
        this.player = document.getElementById(id);
 | 
					        this.player = document.getElementById(id);
 | 
				
			||||||
        this.audio = this.player.querySelector('audio');
 | 
					        this.audio = this.player.querySelector('audio');
 | 
				
			||||||
@ -398,12 +423,12 @@ player = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.audio.addEventListener('timeupdate', function() {
 | 
					        this.audio.addEventListener('timeupdate', function() {
 | 
				
			||||||
            if(self.audio.seekable.length)
 | 
					            if(self.audio.seekable.length)
 | 
				
			||||||
                playerStore.set('stream.' + self.item.stream + '.pos',
 | 
					                PlayerStore.set('stream.' + self.item.stream + '.pos',
 | 
				
			||||||
                                self.audio.currentTime)
 | 
					                                self.audio.currentTime)
 | 
				
			||||||
        }, false);
 | 
					        }, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.audio.addEventListener('ended', function() {
 | 
					        this.audio.addEventListener('ended', function() {
 | 
				
			||||||
            playerStore.set('streams.' + self.item.stream + '.pos')
 | 
					            PlayerStore.set('streams.' + self.item.stream + '.pos')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            single = self.player.querySelector('input.single');
 | 
					            single = self.player.querySelector('input.single');
 | 
				
			||||||
            if(!single.checked)
 | 
					            if(!single.checked)
 | 
				
			||||||
@ -413,7 +438,7 @@ player = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    __init_playlists: function() {
 | 
					    __init_playlists: function() {
 | 
				
			||||||
        this.playlists = this.player.querySelector('.playlists');
 | 
					        this.playlists = this.player.querySelector('.playlists');
 | 
				
			||||||
        this.live = new Playlist(this,
 | 
					        this.live = new Playlist(
 | 
				
			||||||
            'live',
 | 
					            'live',
 | 
				
			||||||
            " {% trans "live" %}",
 | 
					            " {% trans "live" %}",
 | 
				
			||||||
            [ {% for sound in live_streams %}
 | 
					            [ {% for sound in live_streams %}
 | 
				
			||||||
@ -421,25 +446,28 @@ player = {
 | 
				
			|||||||
                  url: "{{ sound.url }}",
 | 
					                  url: "{{ sound.url }}",
 | 
				
			||||||
                  stream: "{{ sound.url }}",
 | 
					                  stream: "{{ sound.url }}",
 | 
				
			||||||
                  info: "{{ sound.info }}",
 | 
					                  info: "{{ sound.info }}",
 | 
				
			||||||
 | 
					                  seekable: false,
 | 
				
			||||||
                }, {% endfor %} ]
 | 
					                }, {% endfor %} ]
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        this.recents = new Playlist(this,
 | 
					        this.recents = new Playlist(
 | 
				
			||||||
            'recents', '{% trans "recents" %}',
 | 
					            'recents', '{% trans "recents" %}',
 | 
				
			||||||
            [ {% for sound in last_sounds %}
 | 
					            [ {% for sound in recents %}
 | 
				
			||||||
                { title: "{{ sound.title }}",
 | 
					                { title: "{{ sound.title }}",
 | 
				
			||||||
                  url: "{{ sound.url }}",
 | 
					                  url: "{{ sound.url }}",
 | 
				
			||||||
                  {% if sound.related.embed %}
 | 
					                  {% if sound.related.embed %}
 | 
				
			||||||
                  embed: "{{ sound.related.embed }}",
 | 
					                  embed: "{{ sound.related.embed }}",
 | 
				
			||||||
                  {% else %}
 | 
					                  {% else %}
 | 
				
			||||||
                  stream: "{{ MEDIA_URL }}{{ sound.related.url|safe }}",
 | 
					                  stream: "{{ sound.related.url|safe }}",
 | 
				
			||||||
                  {% endif %}
 | 
					                  {% endif %}
 | 
				
			||||||
                  info: "{{ sound.related.duration|date:"i:s" }}",
 | 
					                  info: "{{ sound.related.duration|date:"H:i:s" }}",
 | 
				
			||||||
                }, {% endfor %} ]
 | 
					                }, {% endfor %} ]
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        this.marked = new Playlist(this,
 | 
					        this.favorites = new Playlist(
 | 
				
			||||||
            'marked', '★ {% trans "marked" %}', null, true);
 | 
					            'favorites', '★ {% trans "favorites" %}', null, true
 | 
				
			||||||
        this.playlist = new Playlist(this,
 | 
					        );
 | 
				
			||||||
            'playlist', '☰ {% trans "playlist" %}', null, true);
 | 
					        this.playlist = new Playlist(
 | 
				
			||||||
 | 
					            'playlist', '☰ {% trans "playlist" %}', null, true
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.select(this.live.items[0], false);
 | 
					        this.select(this.live.items[0], false);
 | 
				
			||||||
        this.select_playlist(this.recents);
 | 
					        this.select_playlist(this.recents);
 | 
				
			||||||
@ -447,7 +475,7 @@ player = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    load: function() {
 | 
					    load: function() {
 | 
				
			||||||
        var data = playerStore.get('player');
 | 
					        var data = PlayerStore.get('Player');
 | 
				
			||||||
        if(!data)
 | 
					        if(!data)
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -461,19 +489,17 @@ player = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    save: function() {
 | 
					    save: function() {
 | 
				
			||||||
        playerStore.set('player', {
 | 
					        PlayerStore.set('player', {
 | 
				
			||||||
            'selected_playlist': this.__playlist && this.__playlist.name,
 | 
					            'selected_playlist': this.__playlist && this.__playlist.name,
 | 
				
			||||||
            'stream':   this.item && this.item.stream,
 | 
					            'stream':   this.item && this.item.stream,
 | 
				
			||||||
            'single':   this.controls.single.checked,
 | 
					            'single':   this.controls.single.checked,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** player actions **/
 | 
					    /** Player actions **/
 | 
				
			||||||
    /// play a given item { title, src }
 | 
					    /// play a given item { title, src }
 | 
				
			||||||
    play: function() {
 | 
					    play: function() {
 | 
				
			||||||
        var player = this.player;
 | 
					 | 
				
			||||||
        var audio = this.audio;
 | 
					        var audio = this.audio;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(audio.paused)
 | 
					        if(audio.paused)
 | 
				
			||||||
            audio.play();
 | 
					            audio.play();
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
@ -481,13 +507,16 @@ player = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __ask_to_seek(item) {
 | 
					    __ask_to_seek(item) {
 | 
				
			||||||
 | 
					        if(!item.seekable)
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var key = 'stream.' + item.stream + '.pos'
 | 
					        var key = 'stream.' + item.stream + '.pos'
 | 
				
			||||||
        var pos = playerStore.get(key);
 | 
					        var pos = PlayerStore.get(key);
 | 
				
			||||||
        if(!pos)
 | 
					        if(!pos)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        if(confirm("{% trans "restart from the last position?" %}"))
 | 
					        if(confirm("{% trans "restart from the last position?" %}"))
 | 
				
			||||||
            this.audio.currentTime = Math.max(pos - 5, 0);
 | 
					            this.audio.currentTime = Math.max(pos - 5, 0);
 | 
				
			||||||
        playerStore.set(key);
 | 
					        PlayerStore.set(key);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// select the current track to play, and start playing it
 | 
					    /// select the current track to play, and start playing it
 | 
				
			||||||
@ -496,7 +525,7 @@ player = {
 | 
				
			|||||||
        var player = this.player;
 | 
					        var player = this.player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(this.item && this.item.playlist)
 | 
					        if(this.item && this.item.playlist)
 | 
				
			||||||
            this.item.playlist.unselect(this, this.item);
 | 
					            this.item.playlist.unselect(this.item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        audio.pause();
 | 
					        audio.pause();
 | 
				
			||||||
        audio.src = item.stream;
 | 
					        audio.src = item.stream;
 | 
				
			||||||
@ -504,7 +533,7 @@ player = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.item = item;
 | 
					        this.item = item;
 | 
				
			||||||
        if(this.item && this.item.playlist)
 | 
					        if(this.item && this.item.playlist)
 | 
				
			||||||
            this.item.playlist.select(this, this.item);
 | 
					            this.item.playlist.select(this.item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        player.querySelectorAll('#simple-player .title')[0]
 | 
					        player.querySelectorAll('#simple-player .title')[0]
 | 
				
			||||||
            .innerHTML = item.title;
 | 
					            .innerHTML = item.title;
 | 
				
			||||||
@ -563,33 +592,12 @@ player = {
 | 
				
			|||||||
         .send();
 | 
					         .send();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      window.setTimeout(function() {
 | 
					      window.setTimeout(function() {
 | 
				
			||||||
        player.update_on_air();
 | 
					        Player.update_on_air();
 | 
				
			||||||
      }, 60000*5);
 | 
					      }, 60000*5);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// add sound actions to a given element
 | 
					 | 
				
			||||||
    add_actions: function(item, container) {
 | 
					 | 
				
			||||||
        Actions.add_action(container, 'sound.mark', item);
 | 
					 | 
				
			||||||
        Actions.add_action(container, 'sound.play', item, item.stream);
 | 
					 | 
				
			||||||
        // TODO: remove from playlist
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Actions.register('sound.mark', '★', '{% trans "add to my playlist" %}',
 | 
					Player.init('player');
 | 
				
			||||||
    function(item) {
 | 
					 | 
				
			||||||
        player.marked.add(item);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Actions.register('sound.play', '▶', '{% trans "listen" %}',
 | 
					 | 
				
			||||||
    function(item) {
 | 
					 | 
				
			||||||
        item = player.playlist.add(item);
 | 
					 | 
				
			||||||
        player.select_playlist(player.playlist);
 | 
					 | 
				
			||||||
        player.select(item, true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
player.init('player');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								website/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								website/utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					def duration_to_str(duration):
 | 
				
			||||||
 | 
					    return duration.strftime(
 | 
				
			||||||
 | 
					        '%H:%M:%S' if duration.hour else '%M:%S'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Reference in New Issue
	
	Block a user