from pydevd_comm import CMD_SET_BREAK, CMD_ADD_EXCEPTION_BREAK import inspect from pydevd_constants import STATE_SUSPEND, GetThreadId, DictContains from pydevd_file_utils import NormFileToServer, GetFileNameAndBaseFromFile from pydevd_breakpoints import LineBreakpoint, get_exception_name import pydevd_vars import traceback import pydev_log from pydevd_frame_utils import add_exception_to_frame, FCode, cached_call, just_raised DJANGO_SUSPEND = 2 class DjangoLineBreakpoint(LineBreakpoint): def __init__(self, file, line, condition, func_name, expression): self.file = file LineBreakpoint.__init__(self, line, condition, func_name, expression) def is_triggered(self, template_frame_file, template_frame_line): return self.file == template_frame_file and self.line == template_frame_line def __str__(self): return "DjangoLineBreakpoint: %s-%d" %(self.file, self.line) def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name): if type == 'django-line': breakpoint = DjangoLineBreakpoint(file, line, condition, func_name, expression) if not hasattr(pydb, 'django_breakpoints'): pydb.django_breakpoints = {} return breakpoint, pydb.django_breakpoints return None def add_exception_breakpoint(plugin, pydb, type, exception): if type == 'django': if not hasattr(pydb, 'django_exception_break'): pydb.django_exception_break = {} pydb.django_exception_break[exception] = True pydb.setTracingForUntracedContexts() return True return False def remove_exception_breakpoint(plugin, pydb, type, exception): if type == 'django': try: del pydb.django_exception_break[exception] return True except: pass return False def get_breakpoints(plugin, pydb, type): if type == 'django-line': return pydb.django_breakpoints return None def _inherits(cls, *names): if cls.__name__ in names: return True inherits_node = False for base in inspect.getmro(cls): if base.__name__ in names: inherits_node = True break return inherits_node def _is_django_render_call(frame): try: name = frame.f_code.co_name if name != 'render': return False if not DictContains(frame.f_locals, 'self'): return False cls = frame.f_locals['self'].__class__ inherits_node = _inherits(cls, 'Node') if not inherits_node: return False clsname = cls.__name__ return clsname != 'TextNode' and clsname != 'NodeList' except: traceback.print_exc() return False def _is_django_context_get_call(frame): try: if not DictContains(frame.f_locals, 'self'): return False cls = frame.f_locals['self'].__class__ return _inherits(cls, 'BaseContext') except: traceback.print_exc() return False def _is_django_resolve_call(frame): try: name = frame.f_code.co_name if name != '_resolve_lookup': return False if not DictContains(frame.f_locals, 'self'): return False cls = frame.f_locals['self'].__class__ clsname = cls.__name__ return clsname == 'Variable' except: traceback.print_exc() return False def _is_django_suspended(thread): return thread.additionalInfo.suspend_type == DJANGO_SUSPEND def suspend_django(mainDebugger, thread, frame, cmd=CMD_SET_BREAK): frame = DjangoTemplateFrame(frame) if frame.f_lineno is None: return None #try: # if thread.additionalInfo.filename == frame.f_code.co_filename and thread.additionalInfo.line == frame.f_lineno: # return None # don't stay twice on the same line #except AttributeError: # pass pydevd_vars.addAdditionalFrameById(GetThreadId(thread), {id(frame): frame}) mainDebugger.setSuspend(thread, cmd) thread.additionalInfo.suspend_type = DJANGO_SUSPEND thread.additionalInfo.filename = frame.f_code.co_filename thread.additionalInfo.line = frame.f_lineno return frame def _find_django_render_frame(frame): while frame is not None and not _is_django_render_call(frame): frame = frame.f_back return frame #======================================================================================================================= # Django Frame #======================================================================================================================= def _read_file(filename): f = open(filename, "r") s = f.read() f.close() return s def _offset_to_line_number(text, offset): curLine = 1 curOffset = 0 while curOffset < offset: if curOffset == len(text): return -1 c = text[curOffset] if c == '\n': curLine += 1 elif c == '\r': curLine += 1 if curOffset < len(text) and text[curOffset + 1] == '\n': curOffset += 1 curOffset += 1 return curLine def _get_source(frame): try: node = frame.f_locals['self'] if hasattr(node, 'source'): return node.source else: pydev_log.error_once("WARNING: Template path is not available. Please set TEMPLATE_DEBUG=True in your settings.py to make " " django template breakpoints working") return None except: pydev_log.debug(traceback.format_exc()) return None def _get_template_file_name(frame): try: source = _get_source(frame) if source is None: pydev_log.debug("Source is None\n") return None fname = source[0].name if fname == '': pydev_log.debug("Source name is %s\n" % fname) return None else: filename, base = GetFileNameAndBaseFromFile(fname) return filename except: pydev_log.debug(traceback.format_exc()) return None def _get_template_line(frame): source = _get_source(frame) file_name = _get_template_file_name(frame) try: return _offset_to_line_number(_read_file(file_name), source[1][0]) except: return None class DjangoTemplateFrame: def __init__(self, frame): file_name = _get_template_file_name(frame) self.back_context = frame.f_locals['context'] self.f_code = FCode('Django Template', file_name) self.f_lineno = _get_template_line(frame) self.f_back = frame self.f_globals = {} self.f_locals = self.collect_context(self.back_context) self.f_trace = None def collect_context(self, context): res = {} try: for d in context.dicts: for k, v in d.items(): res[k] = v except AttributeError: pass return res def changeVariable(self, name, value): for d in self.back_context.dicts: for k, v in d.items(): if k == name: d[k] = value def _is_django_exception_break_context(frame): try: name = frame.f_code.co_name except: name = None return name in ['_resolve_lookup', 'find_template'] #======================================================================================================================= # Django Step Commands #======================================================================================================================= def can_not_skip(plugin, mainDebugger, pydb_frame, frame): if hasattr(mainDebugger, 'django_breakpoints') and mainDebugger.django_breakpoints and cached_call(pydb_frame, _is_django_render_call, frame): filename = _get_template_file_name(frame) django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename) if django_breakpoints_for_file: return True return False def has_exception_breaks(plugin, mainDebugger): return hasattr(mainDebugger, 'django_exception_break') and mainDebugger.django_exception_break def cmd_step_into(plugin, mainDebugger, frame, event, args, stop_info): mainDebugger, filename, info, thread = args if _is_django_suspended(thread): #stop_info['django_stop'] = event == 'call' and cached_call(frame, is_django_render_call) stop_info['stop'] = stop_info['stop'] and _is_django_resolve_call(frame.f_back) and not _is_django_context_get_call(frame) if stop_info['stop']: info.pydev_django_resolve_frame = 1 #we remember that we've go into python code from django rendering frame def cmd_step_over(plugin, mainDebugger, frame, event, args, stop_info): mainDebugger, filename, info, thread = args if _is_django_suspended(thread): stop_info['django_stop'] = event == 'call' and _is_django_render_call(frame) stop_info['stop'] = False return True else: if event == 'return' and info.pydev_django_resolve_frame is not None and _is_django_resolve_call(frame.f_back): #we return to Django suspend mode and should not stop before django rendering frame info.pydev_step_stop = info.pydev_django_resolve_frame info.pydev_django_resolve_frame = None thread.additionalInfo.suspend_type = DJANGO_SUSPEND stop_info['stop'] = info.pydev_step_stop is frame and event in ('line', 'return') return False def stop(plugin, mainDebugger, frame, event, args, stop_info, arg, step_cmd): mainDebugger, filename, info, thread = args if DictContains(stop_info, 'django_stop') and stop_info['django_stop']: frame = suspend_django(mainDebugger, thread, frame, step_cmd) if frame: mainDebugger.doWaitSuspend(thread, frame, event, arg) return True return False def get_breakpoint(plugin, mainDebugger, pydb_frame, frame, event, args): mainDebugger, filename, info, thread = args flag = False django_breakpoint = None new_frame = None if event == 'call' and info.pydev_state != STATE_SUSPEND and hasattr(mainDebugger, 'django_breakpoints') and \ mainDebugger.django_breakpoints and cached_call(pydb_frame, _is_django_render_call, frame): filename = _get_template_file_name(frame) pydev_log.debug("Django is rendering a template: %s\n" % filename) django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename) if django_breakpoints_for_file: pydev_log.debug("Breakpoints for that file: %s\n" % django_breakpoints_for_file) template_line = _get_template_line(frame) pydev_log.debug("Tracing template line: %d\n" % template_line) if DictContains(django_breakpoints_for_file, template_line): django_breakpoint = django_breakpoints_for_file[template_line] flag = True new_frame = DjangoTemplateFrame(frame) return flag, django_breakpoint, new_frame def suspend(plugin, mainDebugger, thread, frame): return suspend_django(mainDebugger, thread, frame) def exception_break(plugin, mainDebugger, pydb_frame, frame, args, arg): mainDebugger, filename, info, thread = args exception, value, trace = arg if hasattr(mainDebugger, 'django_exception_break') and mainDebugger.django_exception_break and \ get_exception_name(exception) in ['VariableDoesNotExist', 'TemplateDoesNotExist', 'TemplateSyntaxError'] and \ just_raised(trace) and _is_django_exception_break_context(frame): render_frame = _find_django_render_frame(frame) if render_frame: suspend_frame = suspend_django(mainDebugger, thread, render_frame, CMD_ADD_EXCEPTION_BREAK) if suspend_frame: add_exception_to_frame(suspend_frame, (exception, value, trace)) flag = True thread.additionalInfo.message = 'VariableDoesNotExist' suspend_frame.f_back = frame frame = suspend_frame return (flag, frame) return None