125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
import inspect
|
|
|
|
from django.template.loader import render_to_string
|
|
from django.http import HttpResponse, Http404
|
|
from django.conf.urls import url
|
|
from django.core.urlresolvers import reverse
|
|
from django.utils.text import slugify
|
|
|
|
|
|
class Exposure:
|
|
"""
|
|
Define an exposure. Look at @expose decorator.
|
|
"""
|
|
name = None
|
|
"""generated view name"""
|
|
pattern = None
|
|
"""url pattern"""
|
|
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
|
|
"""
|
|
Back ref to the exposed item, can be used to detect inheritance of
|
|
exposed classes.
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
self.__dict__.update(kwargs)
|
|
|
|
@staticmethod
|
|
def gather(cl):
|
|
"""
|
|
Prepare all exposure declared in self.cl, create urls and return
|
|
them. This is done at this place in order to allow sub-classing
|
|
of exposed classes.
|
|
"""
|
|
def view(request, key, *args, fn = None, **kwargs):
|
|
if not fn:
|
|
if not hasattr(cl, key):
|
|
raise Http404()
|
|
|
|
fn = getattr(cl, key)
|
|
if not hasattr(fn, '_exposure'):
|
|
raise Http404()
|
|
|
|
exp = fn._exposure
|
|
res = fn(request, *args, **kwargs)
|
|
if res and exp.template_name:
|
|
ctx = res or {}
|
|
ctx.update({
|
|
'embed': True,
|
|
'exp': cl._exposure,
|
|
})
|
|
res = render_to_string(exp.template_name,
|
|
ctx, request = request)
|
|
return HttpResponse(res or '')
|
|
|
|
# id = str(uuid.uuid1())
|
|
exp = cl._exposure
|
|
exp.pattern = '{name}/{id}'.format(name = exp.name, id = id(cl))
|
|
exp.name = 'exps.{name}.{id}'.format(name = exp.name, id = id(cl))
|
|
|
|
urls = []
|
|
|
|
for name, fn in inspect.getmembers(cl):
|
|
if name.startswith('__') or not hasattr(fn, '_exposure'):
|
|
continue
|
|
fn_exp = fn._exposure
|
|
if not fn_exp.pattern:
|
|
continue
|
|
|
|
name = fn_exp.name or name
|
|
pattern = exp.pattern + '/(?P<key>{name})/{pattern}'.format(
|
|
name = name, pattern = fn_exp.pattern
|
|
)
|
|
|
|
urls.append(url(
|
|
pattern, name = exp.name, view = view,
|
|
kwargs = { 'fn': fn }
|
|
))
|
|
|
|
urls.append(url(
|
|
exp.pattern + '(?P<key>\w+)', name = exp.name, view = view
|
|
))
|
|
return urls
|
|
|
|
|
|
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, *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)
|
|
item._exposure = exp;
|
|
return item
|
|
|