18 """abstract views and templates classes for CubicWeb web client""" |
18 """abstract views and templates classes for CubicWeb web client""" |
19 |
19 |
20 |
20 |
21 from cubicweb import _ |
21 from cubicweb import _ |
22 |
22 |
|
23 import re |
23 from io import BytesIO |
24 from io import BytesIO |
24 from warnings import warn |
25 from warnings import warn |
25 from functools import partial |
26 from functools import partial |
|
27 from inspect import getframeinfo, stack |
26 |
28 |
27 from logilab.common.registry import yes |
29 from logilab.common.registry import yes |
28 from logilab.mtconverter import xml_escape |
30 from logilab.mtconverter import xml_escape |
29 |
31 |
30 from rql import nodes |
32 from rql import nodes |
43 TRANSITIONAL_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' |
45 TRANSITIONAL_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' |
44 TRANSITIONAL_DOCTYPE = TRANSITIONAL_DOCTYPE_NOEXT # bw compat |
46 TRANSITIONAL_DOCTYPE = TRANSITIONAL_DOCTYPE_NOEXT # bw compat |
45 |
47 |
46 STRICT_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' |
48 STRICT_DOCTYPE_NOEXT = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' |
47 STRICT_DOCTYPE = STRICT_DOCTYPE_NOEXT # bw compat |
49 STRICT_DOCTYPE = STRICT_DOCTYPE_NOEXT # bw compat |
|
50 |
|
51 |
|
52 def inject_html_generating_call_on_w(): |
|
53 View.debug_html_rendering = True |
|
54 |
48 |
55 |
49 # base view object ############################################################ |
56 # base view object ############################################################ |
50 |
57 |
51 class View(AppObject): |
58 class View(AppObject): |
52 """This class is an abstraction of a view class, used as a base class for |
59 """This class is an abstraction of a view class, used as a base class for |
84 # content_type = 'application/xhtml+xml' # text/xhtml' |
91 # content_type = 'application/xhtml+xml' # text/xhtml' |
85 binary = False |
92 binary = False |
86 add_to_breadcrumbs = True |
93 add_to_breadcrumbs = True |
87 category = 'view' |
94 category = 'view' |
88 paginable = True |
95 paginable = True |
|
96 debug_html_rendering = False |
89 |
97 |
90 def __init__(self, req=None, rset=None, **kwargs): |
98 def __init__(self, req=None, rset=None, **kwargs): |
91 super(View, self).__init__(req, rset=rset, **kwargs) |
99 super(View, self).__init__(req, rset=rset, **kwargs) |
92 self.w = None |
100 self._w = None |
93 |
101 |
94 @property |
102 @property |
95 def content_type(self): |
103 def content_type(self): |
96 return self._cw.html_content_type() |
104 return self._cw.html_content_type() |
97 |
105 |
|
106 def w(self, text): |
|
107 if self._w is None: |
|
108 raise Exception('Error: call to %s.w before it has been set' % self) |
|
109 |
|
110 if self.debug_html_rendering: |
|
111 caller = getframeinfo(stack()[1][0]) |
|
112 |
|
113 if isinstance(text, str) and re.match(r"^ *<[a-zA-Z0-9]+( .*>|>)", text): |
|
114 to_add = 'cubicweb-generated-by="%s.%s" cubicweb-from-source="%s:%s"' % ( |
|
115 self.__module__, self.__class__.__name__, |
|
116 caller.filename, caller.lineno, |
|
117 ) |
|
118 |
|
119 before_space, beginning_of_html = text.split("<", 1) |
|
120 |
|
121 # when it's a tag without attribues like "<b>" |
|
122 if re.match(r"^ *<[a-zA-Z0-9]+>", text): |
|
123 tag_name, rest = beginning_of_html.split(">", 1) |
|
124 rest = ">" + rest |
|
125 else: |
|
126 tag_name, rest = beginning_of_html.split(" ", 1) |
|
127 rest = " " + rest |
|
128 |
|
129 text = "%(before_space)s<%(tag_name)s %(to_add)s%(rest)s" % { |
|
130 "before_space": before_space, |
|
131 "tag_name": tag_name, |
|
132 "to_add": to_add, |
|
133 "rest": rest, |
|
134 } |
|
135 |
|
136 return self._w(text) |
|
137 |
98 def set_stream(self, w=None): |
138 def set_stream(self, w=None): |
99 if self.w is not None: |
139 if self._w is not None: |
100 return |
140 return |
101 if w is None: |
141 if w is None: |
102 if self.binary: |
142 if self.binary: |
103 self._stream = stream = BytesIO() |
143 self._stream = stream = BytesIO() |
104 else: |
144 else: |
105 self._stream = stream = UStringIO() |
145 self._stream = stream = UStringIO() |
106 w = stream.write |
146 w = stream.write |
107 else: |
147 else: |
108 stream = None |
148 stream = None |
109 self.w = w |
149 self._w = w |
110 return stream |
150 return stream |
111 |
151 |
112 # main view interface ##################################################### |
152 # main view interface ##################################################### |
113 |
153 |
114 def render(self, w=None, **context): |
154 def render(self, w=None, **context): |
236 # view utilities ########################################################## |
276 # view utilities ########################################################## |
237 |
277 |
238 def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs): |
278 def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs): |
239 """shortcut to self.view method automatically passing self.w as argument |
279 """shortcut to self.view method automatically passing self.w as argument |
240 """ |
280 """ |
241 self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs) |
281 self._cw.view(__vid, rset, __fallback_vid, w=self._w, **kwargs) |
242 |
282 |
243 def whead(self, data): |
283 def whead(self, data): |
244 self._cw.html_headers.write(data) |
284 self._cw.html_headers.write(data) |
245 |
285 |
246 def wdata(self, data): |
286 def wdata(self, data): |
464 """ |
504 """ |
465 |
505 |
466 doctype = '<!DOCTYPE html>' |
506 doctype = '<!DOCTYPE html>' |
467 |
507 |
468 def set_stream(self, w=None): |
508 def set_stream(self, w=None): |
469 if self.w is not None: |
509 if self._w is not None: |
470 return |
510 return |
471 if w is None: |
511 if w is None: |
472 if self.binary: |
512 if self.binary: |
473 self._stream = stream = BytesIO() |
513 self._stream = stream = BytesIO() |
474 else: |
514 else: |
475 self._stream = stream = HTMLStream(self._cw) |
515 self._stream = stream = HTMLStream(self._cw) |
476 w = stream.write |
516 w = stream.write |
477 else: |
517 else: |
478 stream = None |
518 stream = None |
479 self.w = w |
519 self._w = w |
480 return stream |
520 return stream |
481 |
521 |
482 def write_doctype(self, xmldecl=True): |
522 def write_doctype(self, xmldecl=True): |
483 assert isinstance(self._stream, HTMLStream) |
523 assert isinstance(self._stream, HTMLStream) |
484 self._stream.doctype = self.doctype |
524 self._stream.doctype = self.doctype |