localstorage; parts becomes expose

This commit is contained in:
bkfox
2016-06-14 14:34:10 +02:00
parent 833e7a551d
commit 9769e0e617
5 changed files with 211 additions and 164 deletions

View File

@ -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

View File

@ -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):

View File

@ -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):
"""