From 30fda77bb7da108ba6363c2b2148d71ad8450f8f Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Tue, 29 Mar 2016 20:43:59 +0300 Subject: [PATCH] PyCharm debugger breaks django form validation (PY-14578) Create a separate resolver for Django forms in order to prevent calling self.errors because it is a property and has side effects. Also We shouldn't call str(), because it also has side effects. --- .../pydev/_pydevd_bundle/pydevd_resolver.py | 59 ++++++++++++------- .../pydev/_pydevd_bundle/pydevd_xml.py | 18 +++++- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py b/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py index 240732983f8a..23f9d444d196 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py @@ -100,9 +100,9 @@ class DefaultResolver: def resolve(self, var, attribute): return getattr(var, attribute) - def get_dictionary(self, var): + def get_dictionary(self, var, names=None): if MethodWrapperType: - return self._getPyDictionary(var) + return self._getPyDictionary(var, names) else: return self._getJyDictionary(var) @@ -162,15 +162,20 @@ class DefaultResolver: return ret - def _getPyDictionary(self, var): + def get_names(self, var): + names = dir(var) + if not names and hasattr(var, '__members__'): + names = var.__members__ + return names + + def _getPyDictionary(self, var, names=None): filterPrivate = False filterSpecial = True filterFunction = True filterBuiltIn = True - names = dir(var) - if not names and hasattr(var, '__members__'): - names = var.__members__ + if not names: + names = self.get_names(var) d = {} #Be aware that the order in which the filters are applied attempts to @@ -482,21 +487,34 @@ class MultiValueDictResolver(DictResolver): raise UnableToResolveVariableException() - def get_dictionary(self, dict): - ret = {} - i = 0 - for key in dict_keys(dict): - val = dict.getlist(key) - i += 1 - #we need to add the id because otherwise we cannot find the real object to get its contents later on. - key = '%s (%s)' % (self.key_to_str(key), id(key)) - ret[key] = val - if i > MAX_ITEMS_TO_HANDLE: - ret[TOO_LARGE_ATTR] = TOO_LARGE_MSG - break - ret['__len__'] = len(dict) - return ret + +#======================================================================================================================= +# DjangoFormResolver +#======================================================================================================================= +class DjangoFormResolver(DefaultResolver): + has_errors_attr = False + + def get_names(self, var): + names = dir(var) + if not names and hasattr(var, '__members__'): + names = var.__members__ + + if "errors" in names: + self.has_errors_attr = True + names.remove("errors") + return names + + def get_dictionary(self, var, names=None): + # Do not call self.errors because it is property and has side effects + d = defaultResolver.get_dictionary(var, self.get_names(var)) + if self.has_errors_attr: + try: + errors_attr = getattr(var, "_errors") + except: + errors_attr = None + d["errors"] = errors_attr + return d #======================================================================================================================= @@ -567,5 +585,6 @@ jyArrayResolver = JyArrayResolver() setResolver = SetResolver() ndarrayResolver = NdArrayResolver() multiValueDictResolver = MultiValueDictResolver() +djangoFormResolver = DjangoFormResolver() dequeResolver = DequeResolver() frameResolver = FrameResolver() diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py index 5381c20722ec..56cd26b1a675 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py @@ -81,6 +81,13 @@ def _update_type_map(): except: pass #django may not be installed + try: + from django.forms import BaseForm + _TYPE_MAP.insert(0, (BaseForm, pydevd_resolver.djangoFormResolver)) + #we should put it before instance resolver + except: + pass #django may not be installed + try: from collections import deque _TYPE_MAP.append((deque, pydevd_resolver.dequeResolver)) @@ -181,6 +188,11 @@ def var_to_xml(val, name, doTrim=True, additionalInXml=''): _type, typeName, resolver = get_type(v) + do_not_call_value_str = False + if isinstance(resolver, pydevd_resolver.djangoFormResolver.__class__): + # do not call str() of Django form objects because has side effects and breaks self.errors + do_not_call_value_str = True + try: if hasattr(v, '__class__'): if v.__class__ == frame_type: @@ -204,7 +216,11 @@ def var_to_xml(val, name, doTrim=True, additionalInXml=''): cName = cName[:-2] except: cName = str(v.__class__) - value = '%s: %s' % (cName, v) + + if do_not_call_value_str: + value = '%s: %r' % (cName, v) + else: + value = '%s: %s' % (cName, v) else: value = str(v) except: