22 | | def get_template_exception_info(exc_type, exc_value, tb): |
23 | | origin, (start, end) = exc_value.source |
24 | | template_source = origin.reload() |
25 | | context_lines = 10 |
26 | | line = 0 |
27 | | upto = 0 |
28 | | source_lines = [] |
29 | | before = during = after = "" |
30 | | for num, next in enumerate(linebreak_iter(template_source)): |
31 | | if start >= upto and end <= next: |
32 | | line = num |
33 | | before = escape(template_source[upto:start]) |
34 | | during = escape(template_source[start:end]) |
35 | | after = escape(template_source[end:next]) |
36 | | source_lines.append( (num, escape(template_source[upto:next])) ) |
37 | | upto = next |
38 | | total = len(source_lines) |
39 | | |
40 | | top = max(1, line - context_lines) |
41 | | bottom = min(total, line + 1 + context_lines) |
42 | | |
43 | | template_info = { |
44 | | 'message': exc_value.args[0], |
45 | | 'source_lines': source_lines[top:bottom], |
46 | | 'before': before, |
47 | | 'during': during, |
48 | | 'after': after, |
49 | | 'top': top, |
50 | | 'bottom': bottom, |
51 | | 'total': total, |
52 | | 'line': line, |
53 | | 'name': origin.name, |
54 | | } |
55 | | exc_info = hasattr(exc_value, 'exc_info') and exc_value.exc_info or (exc_type, exc_value, tb) |
56 | | return exc_info + (template_info,) |
57 | | |
77 | | def get_traceback_html(request, exc_type, exc_value, tb): |
78 | | "Return HTML code for traceback." |
79 | | template_info = None |
80 | | template_does_not_exist = False |
81 | | loader_debug_info = None |
82 | | |
83 | | # Handle deprecated string exceptions |
84 | | if isinstance(exc_type, basestring): |
85 | | exc_value = Exception('Deprecated String Exception: %r' % exc_type) |
86 | | exc_type = type(exc_value) |
87 | | |
88 | | if issubclass(exc_type, TemplateDoesNotExist): |
89 | | from django.template.loader import template_source_loaders |
90 | | template_does_not_exist = True |
91 | | loader_debug_info = [] |
92 | | for loader in template_source_loaders: |
| 42 | class ExceptionReporter: |
| 43 | """ |
| 44 | A class to organize and coordinate reporting on exceptions. |
| 45 | """ |
| 46 | def __init__(self, request, exc_type, exc_value, tb): |
| 47 | self.request = request |
| 48 | self.exc_type = exc_type |
| 49 | self.exc_value = exc_value |
| 50 | self.tb = tb |
| 51 | |
| 52 | self.template_info = None |
| 53 | self.template_does_not_exist = False |
| 54 | self.loader_debug_info = None |
| 55 | |
| 56 | # Handle deprecated string exceptions |
| 57 | if isinstance(self.exc_type, basestring): |
| 58 | self.exc_value = Exception('Deprecated String Exception: %r' % self.exc_type) |
| 59 | self.exc_type = type(self.exc_value) |
| 60 | |
| 61 | def get_traceback_html(self): |
| 62 | "Return HTML code for traceback." |
| 63 | |
| 64 | if issubclass(self.exc_type, TemplateDoesNotExist): |
| 65 | from django.template.loader import template_source_loaders |
| 66 | self.template_does_not_exist = True |
| 67 | self.loader_debug_info = [] |
| 68 | for loader in template_source_loaders: |
| 69 | try: |
| 70 | source_list_func = getattr(__import__(loader.__module__, {}, {}, ['get_template_sources']), 'get_template_sources') |
| 71 | # NOTE: This assumes exc_value is the name of the template that |
| 72 | # the loader attempted to load. |
| 73 | template_list = [{'name': t, 'exists': os.path.exists(t)} \ |
| 74 | for t in source_list_func(str(self.exc_value))] |
| 75 | except (ImportError, AttributeError): |
| 76 | template_list = [] |
| 77 | self.loader_debug_info.append({ |
| 78 | 'loader': loader.__module__ + '.' + loader.__name__, |
| 79 | 'templates': template_list, |
| 80 | }) |
| 81 | if settings.TEMPLATE_DEBUG and hasattr(self.exc_value, 'source'): |
| 82 | self.get_template_exception_info() |
| 83 | |
| 84 | frames = self.get_traceback_frames() |
| 85 | |
| 86 | unicode_hint = '' |
| 87 | if issubclass(self.exc_type, UnicodeError): |
| 88 | start = getattr(self.exc_value, 'start', None) |
| 89 | end = getattr(self.exc_value, 'end', None) |
| 90 | if start is not None and end is not None: |
| 91 | unicode_str = self.exc_value.args[1] |
| 92 | unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') |
| 93 | from django import get_version |
| 94 | t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') |
| 95 | c = Context({ |
| 96 | 'exception_type': self.exc_type.__name__, |
| 97 | 'exception_value': smart_unicode(self.exc_value, errors='replace'), |
| 98 | 'unicode_hint': unicode_hint, |
| 99 | 'frames': frames, |
| 100 | 'lastframe': frames[-1], |
| 101 | 'request': self.request, |
| 102 | 'request_protocol': self.request.is_secure() and "https" or "http", |
| 103 | 'settings': get_safe_settings(), |
| 104 | 'sys_executable': sys.executable, |
| 105 | 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], |
| 106 | 'server_time': datetime.datetime.now(), |
| 107 | 'django_version_info': get_version(), |
| 108 | 'sys_path' : sys.path, |
| 109 | 'template_info': self.template_info, |
| 110 | 'template_does_not_exist': self.template_does_not_exist, |
| 111 | 'loader_debug_info': self.loader_debug_info, |
| 112 | }) |
| 113 | return t.render(c) |
| 114 | |
| 115 | def get_template_exception_info(self): |
| 116 | origin, (start, end) = self.exc_value.source |
| 117 | template_source = origin.reload() |
| 118 | context_lines = 10 |
| 119 | line = 0 |
| 120 | upto = 0 |
| 121 | source_lines = [] |
| 122 | before = during = after = "" |
| 123 | for num, next in enumerate(linebreak_iter(template_source)): |
| 124 | if start >= upto and end <= next: |
| 125 | line = num |
| 126 | before = escape(template_source[upto:start]) |
| 127 | during = escape(template_source[start:end]) |
| 128 | after = escape(template_source[end:next]) |
| 129 | source_lines.append( (num, escape(template_source[upto:next])) ) |
| 130 | upto = next |
| 131 | total = len(source_lines) |
| 132 | |
| 133 | top = max(1, line - context_lines) |
| 134 | bottom = min(total, line + 1 + context_lines) |
| 135 | |
| 136 | self.template_info = { |
| 137 | 'message': self.exc_value.args[0], |
| 138 | 'source_lines': source_lines[top:bottom], |
| 139 | 'before': before, |
| 140 | 'during': during, |
| 141 | 'after': after, |
| 142 | 'top': top, |
| 143 | 'bottom': bottom, |
| 144 | 'total': total, |
| 145 | 'line': line, |
| 146 | 'name': origin.name, |
| 147 | } |
| 148 | if hasattr(self.exc_value, 'exc_info') and self.exc_value.exc_info: |
| 149 | exc_type, exc_value, tb = self.exc_value.exc_info |
| 150 | |
| 151 | def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None): |
| 152 | """ |
| 153 | Returns context_lines before and after lineno from file. |
| 154 | Returns (pre_context_lineno, pre_context, context_line, post_context). |
| 155 | """ |
| 156 | source = None |
| 157 | if loader is not None and hasattr(loader, "get_source"): |
| 158 | source = loader.get_source(module_name) |
| 159 | if source is not None: |
| 160 | source = source.splitlines() |
| 161 | if source is None: |
94 | | source_list_func = getattr(__import__(loader.__module__, {}, {}, ['get_template_sources']), 'get_template_sources') |
95 | | # NOTE: This assumes exc_value is the name of the template that |
96 | | # the loader attempted to load. |
97 | | template_list = [{'name': t, 'exists': os.path.exists(t)} \ |
98 | | for t in source_list_func(str(exc_value))] |
99 | | except (ImportError, AttributeError): |
100 | | template_list = [] |
101 | | loader_debug_info.append({ |
102 | | 'loader': loader.__module__ + '.' + loader.__name__, |
103 | | 'templates': template_list, |
104 | | }) |
105 | | if settings.TEMPLATE_DEBUG and hasattr(exc_value, 'source'): |
106 | | exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb) |
107 | | frames = [] |
108 | | while tb is not None: |
109 | | # support for __traceback_hide__ which is used by a few libraries |
110 | | # to hide internal frames. |
111 | | if tb.tb_frame.f_locals.get('__traceback_hide__'): |
| 163 | f = open(filename) |
| 164 | try: |
| 165 | source = f.readlines() |
| 166 | finally: |
| 167 | f.close() |
| 168 | except (OSError, IOError): |
| 169 | pass |
| 170 | if source is None: |
| 171 | return None, [], None, [] |
| 172 | |
| 173 | encoding = 'ascii' |
| 174 | for line in source[:2]: |
| 175 | # File coding may be specified. Match pattern from PEP-263 |
| 176 | # (http://www.python.org/dev/peps/pep-0263/) |
| 177 | match = re.search(r'coding[:=]\s*([-\w.]+)', line) |
| 178 | if match: |
| 179 | encoding = match.group(1) |
| 180 | break |
| 181 | source = [unicode(sline, encoding, 'replace') for sline in source] |
| 182 | |
| 183 | lower_bound = max(0, lineno - context_lines) |
| 184 | upper_bound = lineno + context_lines |
| 185 | |
| 186 | pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] |
| 187 | context_line = source[lineno].strip('\n') |
| 188 | post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] |
| 189 | |
| 190 | return lower_bound, pre_context, context_line, post_context |
| 191 | |
| 192 | def get_traceback_frames(self): |
| 193 | frames = [] |
| 194 | tb = self.tb |
| 195 | while tb is not None: |
| 196 | # support for __traceback_hide__ which is used by a few libraries |
| 197 | # to hide internal frames. |
| 198 | if tb.tb_frame.f_locals.get('__traceback_hide__'): |
| 199 | tb = tb.tb_next |
| 200 | continue |
| 201 | filename = tb.tb_frame.f_code.co_filename |
| 202 | function = tb.tb_frame.f_code.co_name |
| 203 | lineno = tb.tb_lineno - 1 |
| 204 | loader = tb.tb_frame.f_globals.get('__loader__') |
| 205 | module_name = tb.tb_frame.f_globals.get('__name__') |
| 206 | pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name) |
| 207 | if pre_context_lineno is not None: |
| 208 | frames.append({ |
| 209 | 'tb': tb, |
| 210 | 'filename': filename, |
| 211 | 'function': function, |
| 212 | 'lineno': lineno + 1, |
| 213 | 'vars': tb.tb_frame.f_locals.items(), |
| 214 | 'id': id(tb), |
| 215 | 'pre_context': pre_context, |
| 216 | 'context_line': context_line, |
| 217 | 'post_context': post_context, |
| 218 | 'pre_context_lineno': pre_context_lineno + 1, |
| 219 | }) |
113 | | continue |
114 | | filename = tb.tb_frame.f_code.co_filename |
115 | | function = tb.tb_frame.f_code.co_name |
116 | | lineno = tb.tb_lineno - 1 |
117 | | loader = tb.tb_frame.f_globals.get('__loader__') |
118 | | module_name = tb.tb_frame.f_globals.get('__name__') |
119 | | pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7, loader, module_name) |
120 | | if pre_context_lineno is not None: |
121 | | frames.append({ |
122 | | 'tb': tb, |
123 | | 'filename': filename, |
124 | | 'function': function, |
125 | | 'lineno': lineno + 1, |
126 | | 'vars': tb.tb_frame.f_locals.items(), |
127 | | 'id': id(tb), |
128 | | 'pre_context': pre_context, |
129 | | 'context_line': context_line, |
130 | | 'post_context': post_context, |
131 | | 'pre_context_lineno': pre_context_lineno + 1, |
132 | | }) |
133 | | tb = tb.tb_next |
134 | | |
135 | | if not frames: |
136 | | frames = [{ |
137 | | 'filename': '<unknown>', |
138 | | 'function': '?', |
139 | | 'lineno': '?', |
140 | | }] |
141 | | |
142 | | unicode_hint = '' |
143 | | if issubclass(exc_type, UnicodeError): |
144 | | start = getattr(exc_value, 'start', None) |
145 | | end = getattr(exc_value, 'end', None) |
146 | | if start is not None and end is not None: |
147 | | unicode_str = exc_value.args[1] |
148 | | unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') |
149 | | from django import get_version |
150 | | t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') |
151 | | c = Context({ |
152 | | 'exception_type': exc_type.__name__, |
153 | | 'exception_value': smart_unicode(exc_value, errors='replace'), |
154 | | 'unicode_hint': unicode_hint, |
155 | | 'frames': frames, |
156 | | 'lastframe': frames[-1], |
157 | | 'request': request, |
158 | | 'request_protocol': request.is_secure() and "https" or "http", |
159 | | 'settings': get_safe_settings(), |
160 | | 'sys_executable': sys.executable, |
161 | | 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], |
162 | | 'server_time': datetime.datetime.now(), |
163 | | 'django_version_info': get_version(), |
164 | | 'sys_path' : sys.path, |
165 | | 'template_info': template_info, |
166 | | 'template_does_not_exist': template_does_not_exist, |
167 | | 'loader_debug_info': loader_debug_info, |
168 | | }) |
169 | | return t.render(c) |
| 221 | |
| 222 | if not frames: |
| 223 | frames = [{ |
| 224 | 'filename': '<unknown>', |
| 225 | 'function': '?', |
| 226 | 'lineno': '?', |
| 227 | 'context_line': '???', |
| 228 | }] |
| 229 | |
| 230 | return frames |
| 231 | |
| 232 | def format_exception(self): |
| 233 | """ |
| 234 | Return the same data as from traceback.format_exception. |
| 235 | """ |
| 236 | import traceback |
| 237 | frames = self.get_traceback_frames() |
| 238 | tb = [ (f['filename'], f['lineno'], f['function'], f['context_line']) for f in frames ] |
| 239 | list = ['Traceback (most recent call last):\n'] |
| 240 | list += traceback.format_list(tb) |
| 241 | list += traceback.format_exception_only(self.exc_type, self.exc_value) |
| 242 | return list |
| 243 | |
202 | | def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): |
203 | | """ |
204 | | Returns context_lines before and after lineno from file. |
205 | | Returns (pre_context_lineno, pre_context, context_line, post_context). |
206 | | """ |
207 | | source = None |
208 | | if loader is not None and hasattr(loader, "get_source"): |
209 | | source = loader.get_source(module_name) |
210 | | if source is not None: |
211 | | source = source.splitlines() |
212 | | if source is None: |
213 | | try: |
214 | | f = open(filename) |
215 | | try: |
216 | | source = f.readlines() |
217 | | finally: |
218 | | f.close() |
219 | | except (OSError, IOError): |
220 | | pass |
221 | | if source is None: |
222 | | return None, [], None, [] |
223 | | |
224 | | encoding = 'ascii' |
225 | | for line in source[:2]: |
226 | | # File coding may be specified. Match pattern from PEP-263 |
227 | | # (http://www.python.org/dev/peps/pep-0263/) |
228 | | match = re.search(r'coding[:=]\s*([-\w.]+)', line) |
229 | | if match: |
230 | | encoding = match.group(1) |
231 | | break |
232 | | source = [unicode(sline, encoding, 'replace') for sline in source] |
233 | | |
234 | | lower_bound = max(0, lineno - context_lines) |
235 | | upper_bound = lineno + context_lines |
236 | | |
237 | | pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] |
238 | | context_line = source[lineno].strip('\n') |
239 | | post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] |
240 | | |
241 | | return lower_bound, pre_context, context_line, post_context |
242 | | |