Merge lp:~alecu/openobject-server/django-tracebacks into lp:openobject-server

Proposed by Alejandro J. Cura
Status: Rejected
Rejected by: Antony Lesuisse (OpenERP)
Proposed branch: lp:~alecu/openobject-server/django-tracebacks
Merge into: lp:openobject-server
Diff against target: 494 lines (+470/-2)
2 files modified
bin/htmltraceback.py (+468/-0)
bin/netsvc.py (+2/-2)
To merge this branch: bzr merge lp:~alecu/openobject-server/django-tracebacks
Reviewer Review Type Date Requested Status
Antony Lesuisse (OpenERP) Disapprove
Fabien (Open ERP) Pending
Review via email: mp+14655@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Alejandro J. Cura (alecu) wrote :

The code is inspired in Django's traceback.

Revision history for this message
Christophe CHAUVET (christophe-chauvet) wrote :

Hi

why i see this (os.environ["DJANGO_SETTINGS_MODULE"]) in the code

Does we installed django dependancies ?

Regards,

Revision history for this message
Alejandro J. Cura (alecu) wrote :

The prototype version of the code used to depend on Django's templating module, so that why that "os.environ" line was there. I've now removed it from the code, and cleaned up a few imports. Thanks Christophe!

1872. By Alejandro J. Cura <email address hidden>

clean up imports

Revision history for this message
Christophe CHAUVET (christophe-chauvet) wrote :

I think there is a license issue, the new license to the trunk branch are AGPLv3

Which license use by Dajngo project ?

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Django's license [1] is the modified BSD license [2] (with no advertising clause)

From what I've researched, you can use BSD code in AGPL projects, but not the other way around: http://fi.am/entry/agplv3-explained/
So it's ok to use this module from OpenObject.

--
[1] http://code.djangoproject.com/browser/django/trunk/LICENSE
[2] http://www.opensource.org/licenses/bsd-license.php

Revision history for this message
Antony Lesuisse (OpenERP) (al-openerp) wrote :

I prefer that we stick to plain text default python traceback so this will never be merged.

review: Disapprove

Unmerged revisions

1872. By Alejandro J. Cura <email address hidden>

clean up imports

1871. By Alejandro J. Cura <email address hidden>

adapted django error template to openerp

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'bin/htmltraceback.py'
2--- bin/htmltraceback.py 1970-01-01 00:00:00 +0000
3+++ bin/htmltraceback.py 2009-11-10 14:04:09 +0000
4@@ -0,0 +1,468 @@
5+#
6+# by Alejandro J. Cura - alecu@gcoop.coop
7+# based on Django's debug.py, copyright notice follows
8+#
9+# Copyright (c) Django Software Foundation and individual contributors.
10+# All rights reserved.
11+#
12+# Redistribution and use in source and binary forms, with or without modification,
13+# are permitted provided that the following conditions are met:
14+#
15+# 1. Redistributions of source code must retain the above copyright notice,
16+# this list of conditions and the following disclaimer.
17+#
18+# 2. Redistributions in binary form must reproduce the above copyright
19+# notice, this list of conditions and the following disclaimer in the
20+# documentation and/or other materials provided with the distribution.
21+#
22+# 3. Neither the name of Django nor the names of its contributors may be used
23+# to endorse or promote products derived from this software without
24+# specific prior written permission.
25+#
26+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
30+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
33+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36+
37+import re
38+import cgi
39+import traceback
40+import string
41+import pprint
42+import datetime
43+
44+UNICODE_HINT_TEMPLATE = """
45+<div id="unicode-hint">
46+ <h2>Unicode error hint</h2>
47+ <p>The string that could not be encoded/decoded was: <strong>${hint}</strong></p>
48+</div>
49+"""
50+
51+FRAME_LINE_TEMPLATE = """
52+ <li onclick="toggle('pre${id}', 'post${id}')">${line}</li>"""
53+
54+FRAME_PRE_HTML = """
55+ <ol start="${pre_context_lineno}" class="pre-context" id="pre${id}">${pre_lines_html}
56+ </ol>
57+"""
58+
59+FRAME_POST_HTML = """
60+ <ol start="${post_context_lineno}" class="post-context" id="post${id}">${post_lines_html}
61+ </ol>
62+"""
63+
64+FRAME_CONTEXT_TEMPLATE = """
65+ <div class="context" id="c${id}">
66+ ${pre_context_html}
67+ <ol start="${lineno}" class="context-line">
68+ <li onclick="toggle('pre${id}', 'post${id}')">${context_line} <span>...</span></li>
69+ </ol>
70+ ${post_context_html}
71+ </div>
72+"""
73+
74+FRAME_VARS_ROW = """
75+ <tr>
76+ <td>${var_name}</td>
77+ <td class="code"><div>${var_value}</div></td>
78+ </tr>
79+"""
80+
81+FRAME_VARS_TEMPLATE = """
82+ <div class="commands">
83+ <a href="#" onclick="return varToggle(this, '${id}')"><span>&#x25b6;</span> Local vars</a>
84+ </div>
85+ <table class="vars" id="v${id}">
86+ <thead>
87+ <tr>
88+ <th>Variable</th>
89+ <th>Value</th>
90+ </tr>
91+ </thead>
92+ <tbody>
93+ ${frame_vars_rows}
94+ </tbody>
95+ </table>
96+"""
97+
98+FRAME_TEMPLATE = """
99+ <li class="frame">
100+ <code>${filename}</code> in <code>${function}</code>
101+ ${frame_context}
102+ ${frame_vars}
103+ </li>
104+"""
105+
106+HTML_TRACEBACK_TEMPLATE = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
107+<html lang="en">
108+<head>
109+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
110+ <meta name="robots" content="NONE,NOARCHIVE"/>
111+ <title>${exception_type}</title>
112+ <style type="text/css">
113+ html * { padding:0; margin:0; }
114+ body * { padding:10px 20px; }
115+ body * * { padding:0; }
116+ body { font:small sans-serif; }
117+ body>div { border-bottom:1px solid #ddd; }
118+ h1 { font-weight:normal; }
119+ h2 { margin-bottom:.8em; }
120+ h2 span { font-size:80%; color:#666; font-weight:normal; }
121+ h3 { margin:1em 0 .5em 0; }
122+ h4 { margin:0 0 .5em 0; font-weight: normal; }
123+ table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
124+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
125+ thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; }
126+ tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
127+ table.vars { margin:5px 0 2px 40px; }
128+ table.vars td, table.req td { font-family:monospace; }
129+ table td.code { width:100%; }
130+ table td.code div { overflow:hidden; }
131+ table.source th { color:#666; }
132+ table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
133+ ul.traceback { list-style-type:none; }
134+ ul.traceback li.frame { margin-bottom:1em; }
135+ div.context { margin: 10px 0; }
136+ div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; }
137+ div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
138+ div.context ol.context-line li { color:black; background-color:#ccc; }
139+ div.context ol.context-line li span { float: right; }
140+ div.commands { margin-left: 40px; }
141+ div.commands a { color:black; text-decoration:none; }
142+ #summary { background: #ffc; }
143+ #summary h2 { font-weight: normal; color: #666; }
144+ #template, #template-not-exist { background:#f6f6f6; }
145+ #unicode-hint { background:#eee; }
146+ #traceback { background:#eee; }
147+ #summary table { border:none; background:transparent; }
148+ .error { background: #ffc; }
149+ .specific { color:#cc3300; font-weight:bold; }
150+ h2 span.commands { font-size:.7em;}
151+ span.commands a:link {color:#5E5694;}
152+ pre.exception_value { font-family: sans-serif; color: #666; font-size: 1.5em; margin: 10px 0 10px 0; }
153+ pre.textdetails {display:none}
154+ </style>
155+ <script type="text/javascript">
156+ //<!--
157+ function getElementsByClassName(oElm, strTagName, strClassName){
158+ // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
159+ var arrElements = (strTagName == "*" && document.all)? document.all :
160+ oElm.getElementsByTagName(strTagName);
161+ var arrReturnElements = new Array();
162+ strClassName = strClassName.replace(/\-/g, "\\-");
163+ var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
164+ var oElement;
165+ for(var i=0; i<arrElements.length; i++){
166+ oElement = arrElements[i];
167+ if(oRegExp.test(oElement.className)){
168+ arrReturnElements.push(oElement);
169+ }
170+ }
171+ return (arrReturnElements)
172+ }
173+ function hideAll(elems) {
174+ for (var e = 0; e < elems.length; e++) {
175+ elems[e].style.display = 'none';
176+ }
177+ }
178+ window.onload = function() {
179+ hideAll(getElementsByClassName(document, 'table', 'vars'));
180+ hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
181+ hideAll(getElementsByClassName(document, 'ol', 'post-context'));
182+ }
183+ function toggle() {
184+ for (var i = 0; i < arguments.length; i++) {
185+ var e = document.getElementById(arguments[i]);
186+ if (e) {
187+ e.style.display = e.style.display == 'none' ? 'block' : 'none';
188+ }
189+ }
190+ return false;
191+ }
192+ function varToggle(link, id) {
193+ toggle('v' + id);
194+ var s = link.getElementsByTagName('span')[0];
195+ var uarr = String.fromCharCode(0x25b6);
196+ var darr = String.fromCharCode(0x25bc);
197+ s.innerHTML = s.innerHTML == uarr ? darr : uarr;
198+ return false;
199+ }
200+ //-->
201+ </script>
202+</head>
203+<body>
204+<pre class="textdetails">
205+${text_details}
206+</pre>
207+<div id="summary">
208+ <h1>${exception_type}</h1>
209+ <pre class="exception_value">${exception_value}</pre>
210+</div>
211+${unicode_hint}
212+<div id="traceback">
213+ <h2>Traceback</h2>
214+ <div id="browserTraceback">
215+ <ul class="traceback">
216+ ${frames}
217+ </ul>
218+ </div>
219+</div>
220+
221+</body>
222+</html>
223+"""
224+
225+class DjangoUnicodeDecodeError(UnicodeDecodeError):
226+ def __init__(self, obj, *args):
227+ self.obj = obj
228+ UnicodeDecodeError.__init__(self, *args)
229+
230+ def __str__(self):
231+ original = UnicodeDecodeError.__str__(self)
232+ return '%s. You passed in %r (%s)' % (original, self.obj,
233+ type(self.obj))
234+
235+def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
236+ if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)):
237+ return s
238+ try:
239+ if not isinstance(s, basestring,):
240+ if hasattr(s, '__unicode__'):
241+ s = unicode(s)
242+ else:
243+ try:
244+ s = unicode(str(s), encoding, errors)
245+ except UnicodeEncodeError:
246+ if not isinstance(s, Exception):
247+ raise
248+ # If we get to here, the caller has passed in an Exception
249+ # subclass populated with non-ASCII data without special
250+ # handling to display as a string. We need to handle this
251+ # without raising a further exception. We do an
252+ # approximation to what the Exception's standard str()
253+ # output should be.
254+ s = ' '.join([force_unicode(arg, encoding, strings_only,
255+ errors) for arg in s])
256+ elif not isinstance(s, unicode):
257+ # Note: We use .decode() here, instead of unicode(s, encoding,
258+ # errors), so that if s is a SafeString, it ends up being a
259+ # SafeUnicode at the end.
260+ s = s.decode(encoding, errors)
261+ except UnicodeDecodeError, e:
262+ raise DjangoUnicodeDecodeError(s, *e.args)
263+ return s
264+
265+def render_template(t, params):
266+ st = string.Template(t)
267+ return st.substitute(params)
268+
269+class ExceptionReporter:
270+ def __init__(self, exc_type, exc_value, tb):
271+ self.exc_type = exc_type
272+ self.exc_value = exc_value
273+ self.tb = tb
274+
275+ # Handle deprecated string exceptions
276+ if isinstance(self.exc_type, basestring):
277+ self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type)
278+ self.exc_type = type(self.exc_value)
279+
280+ def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None):
281+ """
282+ Returns context_lines before and after lineno from file.
283+ Returns (pre_context_lineno, pre_context, context_line, post_context).
284+ """
285+ source = None
286+ if loader is not None and hasattr(loader, "get_source"):
287+ source = loader.get_source(module_name)
288+ if source is not None:
289+ source = source.splitlines()
290+ if source is None:
291+ try:
292+ f = open(filename)
293+ try:
294+ source = f.readlines()
295+ finally:
296+ f.close()
297+ except (OSError, IOError):
298+ pass
299+ if source is None:
300+ return None, [], None, []
301+
302+ encoding = 'ascii'
303+ for line in source[:2]:
304+ # File coding may be specified. Match pattern from PEP-263
305+ # (http://www.python.org/dev/peps/pep-0263/)
306+ match = re.search(r'coding[:=]\s*([-\w.]+)', line)
307+ if match:
308+ encoding = match.group(1)
309+ break
310+ source = [unicode(sline, encoding, 'replace') for sline in source]
311+
312+ lower_bound = max(0, lineno - context_lines)
313+ upper_bound = lineno + context_lines
314+
315+ pre_context = [line.strip('\n') for line in source[lower_bound:lineno]]
316+ context_line = source[lineno].strip('\n')
317+ post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]]
318+
319+ return lower_bound, pre_context, context_line, post_context
320+
321+ def get_traceback_frames(self):
322+ frames = []
323+ tb = self.tb
324+ while tb is not None:
325+ # support for __traceback_hide__ which is used by a few libraries
326+ # to hide internal frames.
327+ if tb.tb_frame.f_locals.get('__traceback_hide__'):
328+ tb = tb.tb_next
329+ continue
330+ filename = tb.tb_frame.f_code.co_filename
331+ function = tb.tb_frame.f_code.co_name
332+ lineno = tb.tb_lineno - 1
333+ loader = tb.tb_frame.f_globals.get('__loader__')
334+ module_name = tb.tb_frame.f_globals.get('__name__')
335+ pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name)
336+ if pre_context_lineno is not None:
337+ frames.append({
338+ 'tb': tb,
339+ 'filename': cgi.escape(filename),
340+ 'function': cgi.escape(function),
341+ 'lineno': lineno + 1,
342+ 'vars': tb.tb_frame.f_locals.items(),
343+ 'id': id(tb),
344+ 'pre_context': pre_context,
345+ 'context_line': context_line,
346+ 'post_context': post_context,
347+ 'pre_context_lineno': pre_context_lineno + 1,
348+ })
349+ tb = tb.tb_next
350+
351+ if not frames:
352+ frames = [{
353+ 'filename': '&lt;unknown&gt;',
354+ 'function': '?',
355+ 'lineno': '?',
356+ 'context_line': '???',
357+ }]
358+
359+ return frames
360+
361+ def get_traceback_html(self):
362+ frames = self.get_traceback_frames()
363+ unicode_hint = ''
364+ if issubclass(self.exc_type, UnicodeError):
365+ start = getattr(self.exc_value, 'start', None)
366+ end = getattr(self.exc_value, 'end', None)
367+ if start is not None and end is not None:
368+ unicode_str = self.exc_value.args[1]
369+ unicode_hint = force_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace')
370+
371+ unicode_hint_html = render_template(UNICODE_HINT_TEMPLATE, {'hint':cgi.escape(unicode_hint)}) if unicode_hint else ""
372+
373+ frames_html = []
374+ for frame in frames:
375+ frame_vars_rows = []
376+
377+ for var_name, var_value in sorted(frame['vars']):
378+ c = {
379+ 'var_name': cgi.escape(var_name),
380+ 'var_value': cgi.escape(pprint.pformat(var_value)),
381+ }
382+ frame_vars_rows.append( render_template(FRAME_VARS_ROW, c) )
383+
384+ c = {
385+ 'id': frame['id'],
386+ 'frame_vars_rows': "".join(frame_vars_rows),
387+ }
388+ frame_vars_html = render_template(FRAME_VARS_TEMPLATE, c)
389+
390+ pre_lines = []
391+ for line in frame['pre_context']:
392+ c = {
393+ 'id': frame['id'],
394+ 'line': cgi.escape(line),
395+ }
396+ pre_lines.append(render_template(FRAME_LINE_TEMPLATE, c))
397+ pre_lines_html = "".join(pre_lines)
398+
399+ c = {
400+ 'id': frame['id'],
401+ 'pre_context_lineno': frame['pre_context_lineno'],
402+ 'pre_lines_html': pre_lines_html,
403+ }
404+ pre_context_html = render_template(FRAME_PRE_HTML, c)
405+
406+ post_lines = []
407+ for line in frame['post_context']:
408+ c = {
409+ 'id': frame['id'],
410+ 'line': cgi.escape(line),
411+ }
412+ post_lines.append(render_template(FRAME_LINE_TEMPLATE, c))
413+ post_lines_html = "".join(pre_lines)
414+
415+ c = {
416+ 'id': frame['id'],
417+ 'post_context_lineno': int(frame['lineno'])+1,
418+ 'post_lines_html': post_lines_html,
419+ }
420+ post_context_html = render_template(FRAME_POST_HTML, c)
421+
422+ c = {
423+ 'id': frame['id'],
424+ 'pre_context_html': pre_context_html if frame['pre_context'] else "",
425+ 'lineno': frame['lineno'],
426+ 'context_line': cgi.escape(frame['context_line']),
427+ 'post_context_html': post_context_html if frame['post_context'] else "",
428+ }
429+ frame_context_html = render_template(FRAME_CONTEXT_TEMPLATE, c)
430+
431+ c = {
432+ 'filename': frame['filename'],
433+ 'function': frame['function'],
434+ 'frame_context': frame_context_html if frame['context_line'] else "",
435+ 'frame_vars': frame_vars_html if frame['vars'] else "",
436+ }
437+ frames_html.append( render_template(FRAME_TEMPLATE, c) )
438+
439+ frames_html = "".join(frames_html)
440+
441+ c = {
442+ 'exception_type': self.exc_type.__name__,
443+ 'exception_value': cgi.escape(force_unicode(self.exc_value, errors='replace')),
444+ 'unicode_hint': unicode_hint_html,
445+ 'frames': frames_html,
446+ 'lastframe': frames[-1],
447+ 'text_details': force_unicode("".join(traceback.format_exception(self.exc_type, self.exc_value, self.tb))),
448+ }
449+ return render_template(HTML_TRACEBACK_TEMPLATE,c)
450+
451+
452+def format_exception(*a):
453+ return ExceptionReporter(*a).get_traceback_html()
454+
455+
456+def test():
457+ def inner_function(s):
458+ a = u"\u26a7".encode("ascii")
459+ assert False
460+
461+ def outer_function(d):
462+ inner_function(d)
463+
464+ try:
465+ outer_function("some string")
466+ except:
467+ import sys
468+ t, v, tb = sys.exc_info()
469+ print format_exception(t, v, tb)
470+
471+if __name__ == "__main__":
472+ test()
473
474=== modified file 'bin/netsvc.py'
475--- bin/netsvc.py 2009-11-06 10:06:23 +0000
476+++ bin/netsvc.py 2009-11-10 14:04:09 +0000
477@@ -216,7 +216,7 @@
478 def quit(cls):
479 cls.cancel(None)
480
481-import traceback
482+import htmltraceback
483
484 class xmlrpc(object):
485 class RpcGateway(object):
486@@ -249,7 +249,7 @@
487 tb = e.traceback
488 else:
489 tb = sys.exc_info()
490- tb_s = "".join(traceback.format_exception(*tb))
491+ tb_s = "".join(htmltraceback.format_exception(*tb))
492 if tools.config['debug_mode']:
493 import pdb
494 pdb.post_mortem(tb[2])