Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(728)

Side by Side Diff: appengine/monorail/framework/template_helpers.py

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is govered by a BSD-style
3 # license that can be found in the LICENSE file or at
4 # https://developers.google.com/open-source/licenses/bsd
5
6 """Some utility classes for interacting with templates."""
7
8 import cgi
9 import cStringIO
10 import httplib
11 import logging
12 import time
13 import types
14
15 from third_party import ezt
16
17 from protorpc import messages
18
19 import settings
20 from framework import framework_constants
21
22
23 _DISPLAY_VALUE_TRAILING_CHARS = 8
24 _DISPLAY_VALUE_TIP_CHARS = 120
25
26
27 class PBProxy(object):
28 """Wraps a Protocol Buffer so it is easy to acceess from a template."""
29
30 def __init__(self, pb):
31 self.__pb = pb
32
33 def __getattr__(self, name):
34 """Make the getters template friendly.
35
36 Psudo-hack alert: When attributes end with _bool, they are converted in
37 to EZT style bools. I.e., if false return None, if true return True.
38
39 Args:
40 name: the name of the attribute to get.
41
42 Returns:
43 The value of that attribute (as an EZT bool if the name ends with _bool).
44 """
45 if name.endswith('_bool'):
46 bool_name = name
47 name = name[0:-5]
48 else:
49 bool_name = None
50
51 # Make it possible for a PBProxy-local attribute to override the protocol
52 # buffer field, or even to allow attributes to be added to the PBProxy that
53 # the protocol buffer does not even have.
54 if name in self.__dict__:
55 if callable(self.__dict__[name]):
56 val = self.__dict__[name]()
57 else:
58 val = self.__dict__[name]
59
60 if bool_name:
61 return ezt.boolean(val)
62 return val
63
64 if bool_name:
65 # return an ezt.boolean for the named field.
66 return ezt.boolean(getattr(self.__pb, name))
67
68 val = getattr(self.__pb, name)
69
70 if isinstance(val, messages.Enum):
71 return int(val) # TODO(jrobbins): use str() instead
72
73 if isinstance(val, messages.Message):
74 return PBProxy(val)
75
76 # Return a list of values whose Message entries
77 # have been wrapped in PBProxies.
78 if isinstance(val, (list, messages.FieldList)):
79 list_to_return = []
80 for v in val:
81 if isinstance(v, messages.Message):
82 list_to_return.append(PBProxy(v))
83 else:
84 list_to_return.append(v)
85 return list_to_return
86
87 return val
88
89 def DebugString(self):
90 """Return a string representation that is useful in debugging."""
91 return 'PBProxy(%s)' % self.__pb
92
93 def __eq__(self, other):
94 # Disable warning about accessing other.__pb.
95 # pylint: disable=protected-access
96 return isinstance(other, PBProxy) and self.__pb == other.__pb
97
98
99 _templates = {}
100
101
102 def GetTemplate(
103 template_path, compress_whitespace=True, eliminate_blank_lines=False,
104 base_format=ezt.FORMAT_HTML):
105 """Make a MonorailTemplate if needed, or reuse one if possible."""
106 key = template_path, compress_whitespace, base_format
107 if key in _templates:
108 return _templates[key]
109
110 template = MonorailTemplate(
111 template_path, compress_whitespace=compress_whitespace,
112 eliminate_blank_lines=eliminate_blank_lines, base_format=base_format)
113 _templates[key] = template
114 return template
115
116
117 class cStringIOUnicodeWrapper(object):
118 """Wrapper on cStringIO.StringIO that encodes unicode as UTF-8 as it goes."""
119
120 def __init__(self):
121 self.buffer = cStringIO.StringIO()
122
123 def write(self, s):
124 if isinstance(s, unicode):
125 utf8_s = s.encode('utf-8')
126 else:
127 utf8_s = s
128 self.buffer.write(utf8_s)
129
130 def getvalue(self):
131 return self.buffer.getvalue()
132
133
134 SNIFFABLE_PATTERNS = {
135 '%PDF-': '%NoNoNo-',
136 }
137
138
139 class MonorailTemplate(object):
140 """A template with additional functionality."""
141
142 def __init__(self, template_path, compress_whitespace=True,
143 eliminate_blank_lines=False, base_format=ezt.FORMAT_HTML):
144 self.template_path = template_path
145 self.template = None
146 self.compress_whitespace = compress_whitespace
147 self.base_format = base_format
148 self.eliminate_blank_lines = eliminate_blank_lines
149
150 def WriteResponse(self, response, data, content_type=None):
151 """Write the parsed and filled in template to http server."""
152 if content_type:
153 response.content_type = content_type
154
155 response.status = data.get('http_response_code', httplib.OK)
156 whole_page = self.GetResponse(data)
157 if data.get('prevent_sniffing'):
158 for sniff_pattern, sniff_replacement in SNIFFABLE_PATTERNS.items():
159 whole_page = whole_page.replace(sniff_pattern, sniff_replacement)
160 start = time.time()
161 response.write(whole_page)
162 logging.info('wrote response in %dms', int((time.time() - start) * 1000))
163
164 def GetResponse(self, data):
165 """Generate the text from the template and return it as a string."""
166 template = self.GetTemplate()
167 start = time.time()
168 buf = cStringIOUnicodeWrapper()
169 template.generate(buf, data)
170 whole_page = buf.getvalue()
171 logging.info('rendering took %dms', int((time.time() - start) * 1000))
172 logging.info('whole_page len is %r', len(whole_page))
173 if self.eliminate_blank_lines:
174 lines = whole_page.split('\n')
175 whole_page = '\n'.join(line for line in lines if line.strip())
176 logging.info('smaller whole_page len is %r', len(whole_page))
177 logging.info('smaller rendering took %dms',
178 int((time.time() - start) * 1000))
179 return whole_page
180
181 def GetTemplate(self):
182 """Parse the EZT template, or return an already parsed one."""
183 # We don't operate directly on self.template to avoid races.
184 template = self.template
185
186 if template is None or settings.dev_mode:
187 start = time.time()
188 template = ezt.Template(
189 fname=self.template_path,
190 compress_whitespace=self.compress_whitespace,
191 base_format=self.base_format)
192 logging.info('parsed in %dms', int((time.time() - start) * 1000))
193 self.template = template
194
195 return template
196
197 def GetTemplatePath(self):
198 """Accessor for the template path specified in the constructor.
199
200 Returns:
201 The string path for the template file provided to the constructor.
202 """
203 return self.template_path
204
205
206 class EZTError(object):
207 """This class is a helper class to pass errors to EZT.
208
209 This class is used to hold information that will be passed to EZT but might
210 be unset. All unset values return None (ie EZT False)
211 Example: page errors
212 """
213
214 def __getattr__(self, _name):
215 """This is the EZT retrieval function."""
216 return None
217
218 def AnyErrors(self):
219 return len(self.__dict__) != 0
220
221 def DebugString(self):
222 return 'EZTError(%s)' % self.__dict__
223
224 def SetError(self, name, value):
225 self.__setattr__(name, value)
226
227 def SetCustomFieldError(self, field_id, value):
228 # This access works because of the custom __getattr__.
229 # pylint: disable=access-member-before-definition
230 # pylint: disable=attribute-defined-outside-init
231 if self.custom_fields is None:
232 self.custom_fields = []
233 self.custom_fields.append(EZTItem(field_id=field_id, message=value))
234
235 any_errors = property(AnyErrors, None)
236
237 def FitUnsafeText(text, length):
238 """Trim some unsafe (unescaped) text to a specific length.
239
240 Three periods are appended if trimming occurs. Note that we cannot use
241 the ellipsis character (&hellip) because this is unescaped text.
242
243 Args:
244 text: the string to fit (ASCII or unicode).
245 length: the length to trim to.
246
247 Returns:
248 An ASCII or unicode string fitted to the given length.
249 """
250 if not text:
251 return ""
252
253 if len(text) <= length:
254 return text
255
256 return text[:length] + '...'
257
258
259 def BytesKbOrMb(num_bytes):
260 """Return a human-readable string representation of a number of bytes."""
261 if num_bytes < 1024:
262 return '%d bytes' % num_bytes # e.g., 128 bytes
263 if num_bytes < 99 * 1024:
264 return '%.1f KB' % (num_bytes / 1024.0) # e.g. 23.4 KB
265 if num_bytes < 1024 * 1024:
266 return '%d KB' % (num_bytes / 1024) # e.g., 219 KB
267 if num_bytes < 99 * 1024 * 1024:
268 return '%.1f MB' % (num_bytes / 1024.0 / 1024.0) # e.g., 21.9 MB
269 return '%d MB' % (num_bytes / 1024 / 1024) # e.g., 100 MB
270
271
272 class EZTItem(object):
273 """A class that makes a collection of fields easily accessible in EZT."""
274
275 def __init__(self, **kwargs):
276 """Store all the given key-value pairs as fields of this object."""
277 vars(self).update(kwargs)
278
279 def __repr__(self):
280 fields = ', '.join('%r: %r' % (k, v) for k, v in
281 sorted(vars(self).iteritems()))
282 return '%s({%s})' % (self.__class__.__name__, fields)
283
284
285 def ExpandLabels(page_data):
286 """If page_data has a 'labels' list, expand it into 'label1', etc.
287
288 Args:
289 page_data: Template data which may include a 'labels' field.
290 """
291 label_list = page_data.get('labels', [])
292 if isinstance(label_list, types.StringTypes):
293 label_list = [label.strip() for label in page_data['labels'].split(',')]
294
295 for i in range(len(label_list)):
296 page_data['label%d' % i] = label_list[i]
297 for i in range(len(label_list), framework_constants.MAX_LABELS):
298 page_data['label%d' % i] = ''
299
300
301 class TextRun(object):
302 """A fragment of user-entered text that needs to be safely displyed."""
303
304 def __init__(self, content, tag=None, href=None):
305 self.content = content
306 self.tag = tag
307 self.href = href
308 self.title = None
309 self.css_class = None
OLDNEW
« no previous file with comments | « appengine/monorail/framework/table_view_helpers.py ('k') | appengine/monorail/framework/test/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698