forked from rc/aircox
136 lines
3.8 KiB
Python
136 lines
3.8 KiB
Python
from django.template.loader import render_to_string
|
|
from django.http import HttpResponse
|
|
from django.conf.urls import url
|
|
from django.core.urlresolvers import reverse
|
|
from django.utils.text import slugify
|
|
|
|
|
|
def template(name):
|
|
"""
|
|
the decorated function returns a context that is used to
|
|
render a template value.
|
|
|
|
* template_name: name of the template to use
|
|
* hide_empty: an empty context returns an empty string
|
|
"""
|
|
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)
|
|
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
|
|
|
|
|