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
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -17,7 +17,8 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from honeypot.decorators import check_honeypot
|
||||
|
||||
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:
|
||||
@ -51,7 +52,7 @@ class Viewable:
|
||||
setattr(Sub, k, v)
|
||||
|
||||
if hasattr(cl, '_exposure'):
|
||||
return decorators.expose(Sub)
|
||||
return expose(Sub)
|
||||
return Sub
|
||||
|
||||
|
||||
@ -224,6 +225,7 @@ class ListItem:
|
||||
image = None
|
||||
info = None
|
||||
url = None
|
||||
actions = None
|
||||
|
||||
css_class = None
|
||||
attrs = None
|
||||
@ -285,7 +287,7 @@ class List(Section):
|
||||
def get_object_list(self):
|
||||
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.
|
||||
Return the object_list that is prepared.
|
||||
@ -302,7 +304,7 @@ class List(Section):
|
||||
instances of Post or ListItem.
|
||||
|
||||
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.
|
||||
"""
|
||||
@ -314,10 +316,11 @@ class List(Section):
|
||||
object_list = self.object_list or self.get_object_list()
|
||||
if not object_list and not self.message_empty:
|
||||
return
|
||||
self.object_list = object_list
|
||||
|
||||
self.object_list = 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.update({
|
||||
@ -500,7 +503,7 @@ class Search(Section):
|
||||
)
|
||||
|
||||
|
||||
@decorators.expose
|
||||
@expose
|
||||
class Calendar(Section):
|
||||
model = None
|
||||
template_name = "aircox/cms/calendar.html"
|
||||
@ -535,11 +538,12 @@ class Calendar(Section):
|
||||
})
|
||||
return context
|
||||
|
||||
@decorators.expose
|
||||
@expose
|
||||
def render_exp(cl, *args, year, month, **kwargs):
|
||||
year = int(year)
|
||||
month = int(month)
|
||||
return cl.render(*args, year = year, month = month, **kwargs)
|
||||
|
||||
render_exp._exposure.name = 'render'
|
||||
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_action: function(item, action_id, data, url) {
|
||||
var action = this.registry[action_id];
|
||||
@ -54,15 +53,15 @@ var Actions = {
|
||||
|
||||
action = Actions.registry[action];
|
||||
if(!action)
|
||||
return
|
||||
return;
|
||||
|
||||
data = item.data || item.dataset;
|
||||
action.handler(data, item);
|
||||
action.handler(item.data || item.dataset, item);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
document.addEventListener('DOMContentLoaded', function(event) {
|
||||
var items = document.querySelectorAll('.action[action]');
|
||||
for(var i = 0; i < items.length; i++) {
|
||||
@ -72,6 +71,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
||||
Actions.init_action(item, action_id, data);
|
||||
}
|
||||
}, false);
|
||||
*/
|
||||
|
||||
|
||||
/// Small utility used to make XMLHttpRequests, and map results on objects.
|
||||
|
@ -60,10 +60,9 @@
|
||||
|
||||
{% if object.actions and 'actions' in list.fields %}
|
||||
<div class="actions">
|
||||
{% for action_id,action_data in object.actions.items %}
|
||||
<a class="action" action="{{action_id}}"
|
||||
{% for k,v in action_data.items %}data-{{ k }}="{{ v }}"{% endfor %}></a>
|
||||
{% endfor %}
|
||||
{% for action in object.actions %}
|
||||
{{ action|safe }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -14,6 +14,14 @@
|
||||
<link rel="stylesheet" href="{% static website.styles %}" type="text/css">
|
||||
{% endif %}
|
||||
<script src="{% static "aircox/cms/scripts.js" %}"></script>
|
||||
|
||||
{% if actions %}
|
||||
<script>
|
||||
{{ actions|safe }}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<title>{% if title %}{{ title|striptags }} - {% endif %}{{ website.name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -2,6 +2,7 @@ from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
import aircox.cms.utils as utils
|
||||
import aircox.cms.actions as actions
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@ -40,8 +41,12 @@ def threads(post, sep = '/'):
|
||||
for post in posts[:-1] if post.published
|
||||
])
|
||||
|
||||
|
||||
@register.filter(name='around')
|
||||
def around(page_num, n):
|
||||
"""
|
||||
Return a range of value around a given number.
|
||||
"""
|
||||
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.http import Http404
|
||||
|
||||
from aircox.cms.actions import Actions
|
||||
import aircox.cms.sections as sections
|
||||
import aircox.cms.sections as sections_
|
||||
sections_ = sections # used for name clashes
|
||||
|
||||
|
||||
class BaseView:
|
||||
@ -101,6 +102,7 @@ class BaseView:
|
||||
for k, v in self.menus.items()
|
||||
if v is not self
|
||||
}
|
||||
context['actions'] = Actions.register_code()
|
||||
context['embed'] = False
|
||||
else:
|
||||
context['embed'] = True
|
||||
@ -163,7 +165,7 @@ class PostListView(BaseView, ListView):
|
||||
|
||||
return qs
|
||||
|
||||
def init_list(self):
|
||||
def prepare_list(self):
|
||||
if not self.list:
|
||||
self.list = sections.List(
|
||||
truncate = 32,
|
||||
@ -180,8 +182,11 @@ class PostListView(BaseView, ListView):
|
||||
if field in self.list.fields
|
||||
]
|
||||
|
||||
# done in list
|
||||
# Actions.make(self.request, object_list = self.object_list)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.init_list()
|
||||
self.prepare_list()
|
||||
self.add_css_class('list')
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
Reference in New Issue
Block a user