From 9769e0e6174f50ff7bb38fd2f0bebdf7f492c858 Mon Sep 17 00:00:00 2001 From: bkfox Date: Tue, 14 Jun 2016 14:34:10 +0200 Subject: [PATCH] localstorage; parts becomes expose --- cms/decorators.py | 188 +++++++++++-------- cms/routes.py | 2 +- cms/website.py | 22 +-- website/sections.py | 7 +- website/templates/aircox/website/player.html | 156 +++++++-------- 5 files changed, 211 insertions(+), 164 deletions(-) diff --git a/cms/decorators.py b/cms/decorators.py index 65829f9..1acf3f3 100644 --- a/cms/decorators.py +++ b/cms/decorators.py @@ -5,75 +5,6 @@ from django.core.urlresolvers import reverse from django.utils.text import slugify -def __part_normalize(value, default): - value = value if value else default - return slugify(value.lower()) - - -def parts(cls, name = None, pattern = None): - """ - the decorated class is a parts class, and contains part - functions. Look `part` decorator doc for more info. - """ - name = __part_normalize(name, cls.__name__) - pattern = __part_normalize(pattern, cls.__name__) - - cls._parts = [] - for part in cls.__dict__.values(): - if not hasattr(part, 'is_part'): - continue - - part.name = name + '_' + part.name - part.pattern = pattern + '/' + part.pattern - part = url(part.pattern, name = part.name, - view = part, kwargs = {'cl': cls}) - cls._parts.append(part) - return cls - - -def part(view, name = None, pattern = None): - """ - A part function is a view that is used to retrieve data dynamically, - e.g. from Javascript with XMLHttpRequest. A part function is a classmethod - that returns a string and has the following signature: - - `part(cl, request, parent, *args, **kwargs)` - - When a section with parts is added to the website, the parts' urls - are added to the website's one and make them available. - - A part function can have the following parameters: - * name: part.name or part.__name__ - * pattern: part.pattern or part.__name__ - - An extra method `url` is added to the part function to return the adequate - url. - - Theses are combined with the containing parts class params such as: - * name: parts.name + '_' + part.name - * pattern: parts.pattern + '/' + part.pattern - - The parts class will have an attribute '_parts' as list of generated - urls. - """ - if hasattr(view, 'is_part'): - return view - - def view_(request, as_str = False, cl = None, *args, **kwargs): - v = view(cl, request, *args, **kwargs) - if as_str: - return v - return HttpResponse(v) - - def url(*args, **kwargs): - return reverse(view_.name, *args, **kwargs) - - view_.name = __part_normalize(name, view.__name__) - view_.pattern = __part_normalize(pattern, view.__name__) - view_.is_part = True - view_.url = url - return view_ - def template(name): """ the decorated function returns a context that is used to @@ -82,16 +13,123 @@ def template(name): * template_name: name of the template to use * hide_empty: an empty context returns an empty string """ - def wrapper(func): - def view_(cl, request, *args, **kwargs): - context = func(cl, request, *args, **kwargs) + def template_(func): + def wrapper(request, *args, **kwargs): + if kwargs.get('cl'): + context = func(kwargs.pop('cl'), request, *args, **kwargs) + else: + context = func(request, *args, **kwargs) if not context: return '' context['embed'] = True return render_to_string(name, context, request=request) - view_.__name__ = func.__name__ - return view_ - return wrapper + return wrapper + return template_ +class Exposure: + """ + Define an exposure. Look at @expose decorator. + """ + name = None + """generated view name""" + pattern = None + """url pattern""" + items = None + """for classes: list of url objects for exposed methods""" + template_name = None + """ + for methods: exposed method return a context to be use with + the given template. The view will be wrapped in @template + """ + item = None + """ + exposed item + """ + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def url(self, *args, **kwargs): + """ + reverse url for this exposure + """ + return reverse(self.name, *args, **kwargs) + + def prefix(self, parent): + """ + prefix exposure with the given parent + """ + self.name = parent.name + '.' + self.name + self.pattern = parent.pattern + '/' + self.pattern + + +def expose(item): + """ + Expose a class and its methods as views. This allows data to be + retrieved dynamiccaly from client (e.g. with javascript). + + To expose a method of a class, you must expose the class, then the + method. + + The exposed method has the following signature: + + `func(cl, request, parent, *args, **kwargs) -> str` + + Data related to the exposure are put in the `_exposure` attribute, + as instance of Exposure. + + To add extra parameter, such as template_name, just update the correct + field in func._exposure, that will be taken in account at the class + decoration. + + The exposed method will be prefix'ed with it's parent class exposure. + + When adding views to a website, the exposure of their sections are + added to the list of url. + """ + def get_attr(attr, default): + v = (hasattr(item, attr) and getattr(item, attr)) or default + return slugify(v.lower()) + + name = get_attr('name', item.__name__) + pattern = get_attr('pattern', item.__name__) + + exp = Exposure(name = name, pattern = pattern, item = item) + + # expose a class container: set _exposure attribute + if type(item) == type: + exp.name = 'exp.' + exp.name + exp.items = [] + + for func in item.__dict__.values(): + if not hasattr(func, '_exposure'): + continue + + sub = func._exposure + sub.prefix(exp) + + # FIXME: template warping lose args + if sub.template_name: + sub.item = template(sub.template_name)(sub.item) + + func = url(sub.pattern, name = sub.name, + view = func, kwargs = {'cl': item}) + exp.items.append(func) + + item._exposure = exp; + return item + # expose a method: wrap it + else: + if hasattr(item, '_exposure'): + del item._exposure + + def wrapper(request, as_str = False, *args, **kwargs): + v = exp.item(request, *args, **kwargs) + if as_str: + return v + return HttpResponse(v) + wrapper._exposure = exp; + return wrapper + diff --git a/cms/routes.py b/cms/routes.py index 447741d..3aa53c3 100644 --- a/cms/routes.py +++ b/cms/routes.py @@ -43,7 +43,7 @@ class Route: @classmethod def get_view_name(cl, name): - return name + '_' + cl.name + return name + '.' + cl.name @classmethod def as_url(cl, name, view, view_kwargs = None): diff --git a/cms/website.py b/cms/website.py index 4fd68d2..2565cc9 100644 --- a/cms/website.py +++ b/cms/website.py @@ -35,8 +35,8 @@ class Website: ## components urls = [] """list of urls generated thourgh registrations""" - parts = [] - """list of registered parts (done through sections registration)""" + exposures = [] + """list of registered exposures (done through sections registration)""" registry = {} """dict of registered models by their name""" @@ -45,8 +45,8 @@ class Website: * menus: a list of menus to add to the website """ self.registry = {} - self.parts = [] - self.urls = [ url(r'^parts/', include(self.parts)) ] + self.exposures = [] + self.urls = [ url(r'^exp/', include(self.exposures)) ] self.menus = {} self.__dict__.update(kwargs) @@ -96,18 +96,18 @@ class Website: model._website = self return name - def register_parts(self, sections): + def register_exposures(self, sections): """ - Register parts that are used in the given sections. + Register exposures that are used in the given sections. """ if not hasattr(sections, '__iter__'): sections = [sections] for section in sections: - if not hasattr(section, '_parts'): + if not hasattr(section, '_exposure'): continue - self.parts += [ - url for url in section._parts + self.exposures += [ + url for url in section._exposure.items if url not in self.urls ] @@ -129,7 +129,7 @@ class Website: view_kwargs['menus'] = self.menus if sections: - self.register_parts(sections) + self.register_exposures(sections) view_kwargs['sections'] = sections view = view.as_view( @@ -172,7 +172,7 @@ class Website: elif menu.position in ('left', 'right'): menu.tag = 'side' self.menus[menu.position] = menu - self.register_parts(menu.sections) + self.register_exposures(menu.sections) def get_menu(self, position): """ diff --git a/website/sections.py b/website/sections.py index 889cfb0..a1d47e0 100644 --- a/website/sections.py +++ b/website/sections.py @@ -12,7 +12,7 @@ import aircox.cms.decorators as decorators import aircox.website.models as models -@decorators.parts +@decorators.expose class Player(sections.Section): """ Display a player that is cool. @@ -24,8 +24,7 @@ class Player(sections.Section): """ #default_sounds - @decorators.part - @decorators.template('aircox/cms/list_item.html') + @decorators.expose def on_air(cl, request): qs = programs.Diffusion.get( now = True, @@ -47,6 +46,8 @@ class Player(sections.Section): 'item': post, 'list': sections.List, } + on_air._exposure.template_name = 'aircox/cms/list_item.html' + def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) diff --git a/website/templates/aircox/website/player.html b/website/templates/aircox/website/player.html index c57ed2e..45b4a65 100644 --- a/website/templates/aircox/website/player.html +++ b/website/templates/aircox/website/player.html @@ -5,50 +5,40 @@ {% block header %}
- +