-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathhtml.py
More file actions
137 lines (106 loc) · 3.45 KB
/
Copy pathhtml.py
File metadata and controls
137 lines (106 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
from functools import wraps
from markupsafe import Markup
from sidekick import import_later, Proxy
from .core import Text, Element, Block, Blob
from .utils.role_dispatch import role_singledispatch, error
django_loader = import_later("django.template.loader")
def render(obj, role=None, **kwargs):
"""
Like :func:`render`, but return a string of HTML code instead of a
Hyperpython object.
"""
return html(obj, role=role, **kwargs).__html__()
@role_singledispatch
def html(obj, role=None, **kwargs):
"""
Convert object into a hyperpython structure.
Args:
obj:
Input object.
role:
Optional description
Additional context variables for the rendering process can be passed as
keyword arguments.
Returns:
A hyperpython object.
"""
# Try the html_role interface.
try:
method = obj.html_role
except AttributeError:
pass
else:
return method(role=role, **kwargs)
# If role is given, we adopt a more strict behavior
if role is not None:
raise error(type(obj), role)
# Fallback to __html__ or string renderers for role-less calls
try:
raw = obj.__html__()
except AttributeError:
return Text(str(obj))
else:
return Blob(raw)
#
# Auxiliary functions
#
def register_template(cls, template, role=None):
"""
Decorator that registers a template-based renderer.
(Currently, only Django is supported).
Args:
cls:
Type of input object.
template:
Template name or list of template names.
role:
Optional role for the template.
Examples:
The decorated function must receive an instance of `cls` as first
argument and any number of keyword arguments. It should return a context
dictionary that is passed to the template to render the final HTML
string.
@render.register_template(User, 'users/user-contact.html', 'contact')
def user_contact(user, **kwargs):
return {
'name': user.name,
'email': user.get_public_email(),
# ...
}
The role can be omitted to register a fallback implementation for the
given model. The fallback receives the passed role as a keyword
argument.
@render.register_template(User, 'users/user-generic.html')
def user_contact(user, role=None, **kwargs):
return {
'user': user,
'role': role,
}
"""
template = django_loader.get_template(template)
renderer = template.render
def decorator(func):
@html.register(cls, role)
def wrapped(obj, **kwargs):
ctx = func(obj, **kwargs)
request = ctx.get("request")
data = renderer(context=ctx, request=request)
return Blob(data)
return wrapped
return decorator
html.register_template = register_template
#
# Register default renderers
#
def no_role(func):
@wraps(func)
def wrapped(x, role=None, **kwargs):
if role is None:
return func(x, **kwargs)
raise error(type(x), role)
return wrapped
html.register(Markup)(no_role(lambda x: Blob(x)))
html.register(str)(no_role(lambda x: Text(x)))
html.register(Proxy)(lambda x, **kwargs: html(x._obj__, **kwargs))
for _cls in (Element, Text, Block):
html.register(_cls)(no_role(lambda x: x))