Commit 2de58ec4 authored by Jérome Perrin's avatar Jérome Perrin

Merge remote-tracking branch 'upstream/master' into zope4py2

parents 341fdcf0 f269e6ca
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>change_function</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>2.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Change Function</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/Base_viewChangeIdDialog</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -44,6 +44,7 @@ Embedded File | fullsize_view
Embedded File | view
Embedded File | web_view
Embedded Folder | view
External Identifier | change_function
External Identifier | view
Fax | change_function
Fax | view
......
......@@ -402,9 +402,9 @@ return printed
'Base_viewGeek',
'View')
form = skin_folder._getOb('Base_viewGeek', None)
form.manage_addField('my_title', 'Title', 'ProxyField')
form.manage_addField('my_proxy_field', 'Proxy', 'ProxyField')
field = form.my_title
field = form.my_proxy_field
self.assertFalse(form.get_fields())
self.assertEqual([field], form.get_fields(include_disabled=True))
......@@ -413,10 +413,22 @@ return printed
self.assertEqual('', field.get_tales('default'))
regexp = '^%s$' % re.escape("Can't find the template field of"
" <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_title>"
" <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_proxy_field>"
% self.portal.getId())
for func in ( field.render
, partial(field.get_value, 'default')
, partial(field.get_recursive_tales, 'default')
):
self.assertRaisesRegexp(BrokenProxyField, regexp, func)
# we can still view the field in ZMI
form.manage_main()
field.manage_main()
# and repair it
form.manage_addField('my_field', 'Title', 'StringField')
field.manage_edit(
{
'field_form_id': 'Base_viewGeek',
'field_field_id': 'my_field',
})
self.assertEqual(field.getTemplateField(), form.my_field)
......@@ -84,7 +84,6 @@ class TestZODBHistory(ERP5TypeTestCase):
self.assertTrue(len(history_list) > 0)
d = history_list[0]
changes = d['changes']
self.assertEqual(changes['portal_type'], 'Organisation')
self.assertEqual(changes['id'], 'org')
self.assertTrue(changes['uid'] is not None)
......@@ -140,7 +139,7 @@ class TestZODBHistory(ERP5TypeTestCase):
document.edit(title='ネクセディ', default_address_city='千代田区')
self.commit()
_, change, = document.Base_getZODBHistoryList()
self.assertIn('title:ネクセディ', change.getProperty('changes'))
self.assertIn('title: ネクセディ', change.getProperty('changes'))
# no encoding error
document.Base_viewZODBHistory()
......@@ -167,9 +166,9 @@ class TestZODBHistory(ERP5TypeTestCase):
document.Base_viewZODBHistory()
change, = document.Base_getZODBHistoryList()
self.assertIn('data:(binary)', change.getProperty('changes'))
self.assertIn('content_type:image/png', change.getProperty('changes'))
self.assertIn('title:ロゴ', change.getProperty('changes'))
self.assertIn('data: (binary)', change.getProperty('changes'))
self.assertIn('content_type: image/png', change.getProperty('changes'))
self.assertIn('title: ロゴ', change.getProperty('changes'))
def test_suite():
......
......@@ -104,6 +104,9 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEqual(test_web.getPortalType(), 'Business Template')
self.assertEqual(test_web.getTitle(), 'test_web')
self.assertEqual(len(test_web.getRevision()), 28)
self.assertEqual(
test_web.getPublicationUrl(),
'http://www.erp5.org/dists/snapshot/test_bt5/test_web.bt5')
def _svn_setup_ssl(self):
"""
......@@ -139,6 +142,8 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEqual(test_web.getPortalType(), 'Business Template')
self.assertEqual(test_web.getTitle(), 'test_web')
self.assertEqual(len(test_web.getRevision()), 28)
self.assertEqual(
test_web.getPublicationUrl(), bt5_url)
def test_00_updateBusinessTemplateFromUrl_simple(self):
"""
......@@ -549,6 +554,7 @@ class TestTemplateTool(ERP5TypeTestCase):
bt = self.templates_tool.getInstalledBusinessTemplate(bt5_name, strict=True)
self.assertNotEquals(bt, None)
self.assertEqual(bt.getTitle(), bt5_name)
self.assertEqual(bt.getPublicationUrl(), self._getBTPathAndIdList([bt5_name])[0][0])
# Repeat operation, the bt5 should be ignored
self.templates_tool.installBusinessTemplateListFromRepository([bt5_name])
......
import itertools
import time
from Products.CMFActivity.Activity.Queue import VALIDATION_ERROR_DELAY
from Products.CMFActivity.ActivityTool import getCurrentNode
def waitForActivities(self, delay=100, count=None):
"""
......@@ -9,13 +10,17 @@ def waitForActivities(self, delay=100, count=None):
RuntimeError is raised in case there is no way
to finish activities.
"""
activity_tool = self.getPortalObject().portal_activities
assert not (
activity_tool.isSubscribed()
and getCurrentNode() in activity_tool.getProcessingNodeList())
if count is not None: # BBB
# completely arbitrary conversion factor: count used to default to 1000
# and I (just as arbitrarily) converted that into a 100s default maximum
# tolerable wait delay before bailing.
delay = count / 10.
deadline = time.time() + delay
activity_tool = self.getPortalObject().portal_activities
for call_count in itertools.count():
x = activity_tool.getMessageList()
if not x:
......
......@@ -786,7 +786,7 @@ class TestUpgrader(ERP5TypeTestCase):
def stepCheckPersonTitleHistory(self, sequence=None):
self.assertEqual(
[x.changes for x in self.portal.person_module['1'].Base_getZODBHistoryList()[-3:]],
[('title:M. pre_upgrade',), ('title:M. upgrader',), ('title:M. post_upgrade',)])
[('title: M. pre_upgrade',), ('title: M. upgrader',), ('title: M. post_upgrade',)])
def test_upgrade_activities_are_run_sequentially(self):
"""
......
......@@ -77,17 +77,18 @@ from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from OFS.Traversable import NotFound
from OFS import SimpleItem
from OFS.Image import Pdata
import coverage
from io import BytesIO
from copy import deepcopy
from zExceptions import BadRequest
from Products.ERP5Type.XMLExportImport import exportXML, customImporters
from Products.ERP5Type.Workflow import WorkflowHistoryList
from zLOG import LOG, WARNING, INFO
from zLOG import LOG, WARNING, INFO, PROBLEM
from warnings import warn
from lxml.etree import parse
from xml.sax.saxutils import escape
from Products.CMFCore.Expression import Expression
from six.moves.urllib.parse import quote, unquote
from six.moves.urllib.parse import quote, unquote, urlparse
from difflib import unified_diff
import posixpath
import transaction
......@@ -97,6 +98,7 @@ import threading
from ZODB.broken import Broken, BrokenModified
from Products.ERP5.genbt5list import BusinessTemplateRevision, \
item_name_list, item_set
from Products.ERP5Type.mixin.component import ComponentMixin
CACHE_DATABASE_PATH = None
try:
......@@ -1156,22 +1158,33 @@ class ObjectTemplateItem(BaseTemplateItem):
"""
pass
def onNewObject(self, obj):
def onNewObject(self, obj, context):
"""
Installation hook.
Called when installation process determined that object to install is
new on current site (it's not replacing an existing object).
`obj` parameter is the newly created object in its acquisition context.
`context` is the business template instance, in its acquisition context.
Can be overridden by subclasses.
"""
pass
if isinstance(obj, (PythonScript, ComponentMixin)) and coverage.Coverage.current():
relative_path = '/'.join(obj.getPhysicalPath()[len(context.getPortalObject().getPhysicalPath()):])
filename = os.path.join(
context.getPublicationUrl(),
self.__class__.__name__,
relative_path + '.py')
if os.path.exists(filename):
obj._erp5_coverage_filename = filename
else:
LOG('BusinessTemplate', PROBLEM, 'Could not find file for %s' % filename)
def onReplaceObject(self, obj):
def onReplaceObject(self, obj, context):
"""
Installation hook.
Called when installation process determined that object to install is
to replace an existing object on current site (it's not new).
`obj` parameter is the replaced object in its acquisition context.
`context` is the business template instance, in its acquisition context.
Can be overridden by subclasses.
"""
pass
......@@ -1416,9 +1429,9 @@ class ObjectTemplateItem(BaseTemplateItem):
if not object_existed:
# A new object was added, call the hook
self.onNewObject(obj)
self.onNewObject(obj, context)
else:
self.onReplaceObject(obj)
self.onReplaceObject(obj, context)
# mark a business template installation so in 'PortalType_afterClone' scripts
# we can implement logical for reseting or not attributes (i.e reference).
......@@ -1972,9 +1985,11 @@ class CategoryTemplateItem(ObjectTemplateItem):
def beforeInstall(self):
self._installed_new_category = False
return super(CategoryTemplateItem, self).beforeInstall()
def onNewObject(self, obj):
def onNewObject(self, obj, context):
self._installed_new_category = True
return super(CategoryTemplateItem, self).onNewObject(obj, context)
def afterInstall(self):
if self._installed_new_category:
......@@ -2392,7 +2407,8 @@ class WorkflowTemplateItem(ObjectTemplateItem):
continue
raise
container_ids = container.objectIds()
if object_id in container_ids: # Object already exists
object_existed = object_id in container_ids
if object_existed:
self._backupObject(action, trashbin, container_path, object_id, keep_subobjects=1)
container.manage_delObjects([object_id])
obj = self._objects[path]
......@@ -2402,6 +2418,11 @@ class WorkflowTemplateItem(ObjectTemplateItem):
obj = container._getOb(object_id)
obj.manage_afterClone(obj)
obj.wl_clearLocks()
if not object_existed:
# A new object was added, call the hook
self.onNewObject(obj, context)
else:
self.onReplaceObject(obj, context)
def uninstall(self, context, **kw):
object_path = kw.get('object_path', None)
......@@ -4218,7 +4239,7 @@ class _ZodbComponentTemplateItem(ObjectTemplateItem):
raise NotImplementedError
def __init__(self, id_list, tool_id='portal_components', **kw):
ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
super(_ZodbComponentTemplateItem, self).__init__(id_list, tool_id=tool_id, **kw)
def isKeepWorkflowObjectLastHistoryOnly(self, path):
"""
......@@ -4248,9 +4269,13 @@ class _ZodbComponentTemplateItem(ObjectTemplateItem):
obj.workflow_history[wf_id] = WorkflowHistoryList([wf_history])
def onNewObject(self, _):
def onNewObject(self, obj, context):
self._do_reset = True
onReplaceObject = onNewObject
return super(_ZodbComponentTemplateItem, self).onNewObject(obj, context)
def onReplaceObject(self, obj, context):
self._do_reset = True
return super(_ZodbComponentTemplateItem, self).onReplaceObject(obj, context)
def afterInstall(self):
"""
......@@ -5718,6 +5743,9 @@ Business Template is a set of definitions, such as skins, portal types and categ
value = self.getProperty(id)
if not value:
continue
if id == 'publication_url':
if urlparse(value).scheme in ('file', ''):
continue
if prop_type in ('text', 'string', 'int', 'boolean'):
bta.addObject(str(value), name=id, path='bt', ext='')
elif prop_type in ('lines', 'tokens'):
......
......@@ -401,6 +401,7 @@ class TemplateTool (BaseTool):
bt = self._download_local(path, id)
bt.build(no_action=True)
bt.setPublicationUrl(url)
return bt
security.declareProtected('Import/Export objects', 'importBase64EncodedText')
......
......@@ -16,7 +16,7 @@ def beautifyChange(change_dict):
six.text_type(property_value, 'utf-8')
except UnicodeDecodeError:
property_value = '(binary)'
change_list.append('{}:{}'.format(property_name, property_value))
change_list.append('{}: {}'.format(property_name, property_value))
return change_list
try:
......
......@@ -171,8 +171,9 @@ class ProxyField(ZMIField):
Surcharged values from proxied field.
"""
# Edit template field attributes
template_field = self.getRecursiveTemplateField()
template_field = self.getTemplateField()
if template_field is not None:
template_field = self.getRecursiveTemplateField()
# Check the surcharged checkboxes
surcharge_list = []
......@@ -581,6 +582,14 @@ class ProxyField(ZMIField):
# ("form_id and field_id don't define a valid template")
pass
security.declareProtected('View', 'title')
def title(self):
"""The title of this field."""
try:
return super(ProxyField, self).title()
except BrokenProxyField:
return 'broken'
security.declareProtected('Access contents information', 'has_value')
def has_value(self, id):
"""
......
......@@ -11,7 +11,7 @@ Surcharge <dtml-var meta_type> properties here.
<form action="manage_edit" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<dtml-let proxy_field="this()"
current_field="proxy_field.getRecursiveTemplateField()">
current_field="None if proxy_field.getTemplateField() is None else proxy_field.getRecursiveTemplateField()">
<!-- First, display ProxyField properties -->
<!-- see: Formulator/dtml/fieldEdit.dtml -->
......
......@@ -68,7 +68,7 @@ This tab can therefore not be used.
<!-- XXX Loop until find not a proxy field -->
<dtml-let proxy_field="this()"
current_field="proxy_field.getRecursiveTemplateField()">
current_field="None if proxy_field.getTemplateField() is None else proxy_field.getRecursiveTemplateField()">
<dtml-if "current_field is not None">
<dtml-let form="current_field.tales_form">
......
......@@ -399,6 +399,7 @@ class ERP5TypeInformation(XMLObject,
self.getId(),
temp=temp_object)
base_ob = klass(id)
assert base_ob.portal_type == self.getId()
ob = base_ob.__of__(container)
if temp_object:
......@@ -422,10 +423,6 @@ class ERP5TypeInformation(XMLObject,
if getattr(base_ob, 'uid', None) is None:
ob.uid = portal.portal_catalog.newUid()
# Portal type has to be set before setting other attributes
# in order to initialize aq_dynamic
ob.portal_type = self.getId()
if compute_local_role:
# Do not reindex object because it's already done by manage_afterAdd
self.updateLocalRolesOnDocument(ob, reindex=False)
......
......@@ -36,6 +36,7 @@ import imp
import collections
from six import reraise
import coverage
from Products.ERP5Type.Utils import ensure_list
from Products.ERP5.ERP5Site import getSite
from Products.ERP5Type import product_path as ERP5Type_product_path
......@@ -333,6 +334,14 @@ class ComponentDynamicPackage(ModuleType):
# This must be set for imports at least (see PEP 302)
module.__file__ = '<' + relative_url + '>'
if coverage.Coverage.current():
if hasattr(component, '_erp5_coverage_filename'):
module.__file__ = component._erp5_coverage_filename
else:
LOG(
"ERP5Type.Tool.ComponentTool",
WARNING,
"No coverage filesystem mapping for %s" % (module_fullname_alias or module_fullname))
# Only useful for get_source(), do it before exec'ing the source code
# so that the source code is properly display in case of error
......
......@@ -420,6 +420,9 @@ class ERP5TypeFunctionalTestCase(ERP5TypeTestCase):
# create browser_id_manager
if not "browser_id_manager" in self.portal.objectIds():
self.portal.manage_addProduct['Sessions'].constructBrowserIdManager()
# unsubscribe from activities, we'll use Zuite_waitForActivities to
# process activities
self.portal.portal_activities.unsubscribe()
self.commit()
self.setSystemPreference()
# non-recursive results clean of portal_tests/ or portal_tests/``run_only``
......
......@@ -140,6 +140,9 @@ def main():
args.test_node_title, suite.allow_restart, test_suite_title,
args.project_title)
if test_result is not None:
os.environ['ERP5_TEST_RESULT_REVISION'] = test_result.revision
os.environ['ERP5_TEST_RESULT_ID'] = test_result.test_result_path.split('/')[-1]
assert revision == test_result.revision, (revision, test_result.revision)
while suite.acquire():
test = test_result.start(suite.running.keys())
......
......@@ -12,10 +12,7 @@ import errno
import random
import transaction
from glob import glob
try:
from coverage import coverage
except ImportError:
coverage = None
WIN = os.name == 'nt'
......@@ -27,8 +24,6 @@ Options:
-v, --verbose produce verbose output
-h, --help this help screen
-p, --profile print profiling results at the end
--coverage=STRING Use the given path as a coverage config file and
thus enable code coverateg report
--portal_id=STRING force id of the portal. Useful when using
--data_fs_path to run tests on an existing
Data.fs
......@@ -640,11 +635,6 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None):
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGHUP, shutdown)
coverage_config = os.environ.get('coverage', None)
if coverage_config:
coverage_process = coverage(config_file=coverage_config)
coverage_process.start()
try:
save = int(os.environ.get('erp5_save_data_fs', 0))
load = int(os.environ.get('erp5_load_data_fs', 0))
......@@ -719,11 +709,6 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None):
# disconnected from it.
wcfs_server.stop()
if coverage_config:
coverage_process.stop()
coverage_process.save()
coverage_process.html_report()
if save and save_mysql:
save_mysql(verbosity)
......@@ -749,7 +734,7 @@ def main(argument_list=None):
sys.argv.extend(old_argv[1:])
try:
opts, args = getopt.getopt(sys.argv[1:],
"hpvD", ["help", "verbose", "profile", "coverage=", "portal_id=",
"hpvD", ["help", "verbose", "profile", "portal_id=",
"data_fs_path=",
"bt5_path=",
"firefox_bin=",
......@@ -812,11 +797,6 @@ def main(argument_list=None):
elif opt == '-D':
debug = 1
os.environ["erp5_debug_mode"] = str(debug)
elif opt == "--coverage":
if coverage:
os.environ['coverage'] = arg
else:
_print("WARNING Coverage module not found")
elif opt in ("-p", "--profile"):
os.environ['PROFILE_TESTS'] = "1"
# profiling of setup and teardown is disabled by default, just set
......
......@@ -1867,7 +1867,8 @@ class Catalog(Folder,
else:
if related_key_definition is not None:
search_key = search_key.getSearchKey(sql_catalog=self,
related_key_definition=related_key_definition)
related_key_definition=related_key_definition,
search_key_name=search_key_name)
return search_key
security.declareProtected(access_contents_information, 'buildSingleQuery')
......@@ -1885,17 +1886,18 @@ class Catalog(Folder,
from it.
"""
return self._buildQuery(
buildQueryFromSearchKey=lambda search_key: search_key.buildQuery(
lambda search_key: search_key.buildQuery(
value,
logical_operator=logical_operator,
comparison_operator=comparison_operator,
),
key=key,
search_key_name=search_key_name,
ignore_unknown_columns=ignore_unknown_columns,
key,
search_key_name,
ignore_unknown_columns,
)
def _buildQueryFromAbstractSyntaxTreeNode(self, node, search_key, wrap, ignore_unknown_columns):
def _buildQueryFromAbstractSyntaxTreeNode(self, node, search_key, wrap,
ignore_unknown_columns):
"""
node
Abstract syntax tree node (see SearchText/AdvancedSearchTextParser.py,
......@@ -1952,7 +1954,10 @@ class Catalog(Folder,
return result
security.declareProtected(access_contents_information, 'buildQueryFromAbstractSyntaxTreeNode')
def buildQueryFromAbstractSyntaxTreeNode(self, node, key, wrap=lambda x: x, ignore_unknown_columns=False):
def buildQueryFromAbstractSyntaxTreeNode(self, node, key,
search_key_name=None,
wrap=lambda x: x,
ignore_unknown_columns=False):
"""
Build a query from given Abstract Syntax Tree (AST) node by recursing in
its childs.
......@@ -1966,17 +1971,18 @@ class Catalog(Folder,
Expected node API is described in interfaces/abstract_syntax_node.py .
"""
return self._buildQuery(
buildQueryFromSearchKey=lambda search_key: self._buildQueryFromAbstractSyntaxTreeNode(
lambda search_key: self._buildQueryFromAbstractSyntaxTreeNode(
node,
search_key,
wrap,
ignore_unknown_columns,
),
key=key,
ignore_unknown_columns=ignore_unknown_columns,
key,
search_key_name,
ignore_unknown_columns,
)
def _buildQuery(self, buildQueryFromSearchKey, key, search_key_name=None, ignore_unknown_columns=False):
def _buildQuery(self, buildQueryFromSearchKey, key, search_key_name, ignore_unknown_columns):
"""
Determine the SearchKey to use to generate a Query, and call buildQueryFromSearchKey with it.
"""
......@@ -1996,7 +2002,7 @@ class Catalog(Folder,
related_key_definition=related_key_definition,
search_key_name=search_key_name,
)
result = buildQueryFromSearchKey(search_key=build_key)
result = buildQueryFromSearchKey(build_key)
if related_key_definition is not None:
result = search_key.buildQuery(sql_catalog=self,
related_key_definition=related_key_definition,
......@@ -2049,32 +2055,35 @@ class Catalog(Folder,
# We have an empty value, do not create a query from it
empty_value_dict[key] = value
else:
if isinstance(value, dict):
# Dictionnary: might contain the search key to use.
search_key_name = value.get('key')
# Backward compatibility: former "Keyword" key is now named
# "KeywordKey".
if search_key_name == 'Keyword':
search_key_name = value['key'] = 'KeywordKey'
# Backward compatibility: former "ExactMatch" is now only available
# as "RawKey"
elif search_key_name == 'ExactMatch':
search_key_name = value['key'] = 'RawKey'
if isinstance(value, BaseQuery):
# Query instance: use as such, ignore key.
result = value
elif isinstance(value, (basestring, dict)):
# String: parse using key's default search key.
raw_value = value
wrap = lambda x: x
if isinstance(value, dict):
# De-wrap value for parsing, and re-wrap when building queries.
def wrap(x):
result = raw_value.copy()
result['query'] = x
return result
# Dictionary: might contain the search key to use.
search_key_name = value.pop('key', None)
# Backward compatibility: former "Keyword" key is now named
# "KeywordKey".
if search_key_name == 'Keyword':
search_key_name = 'KeywordKey'
# Backward compatibility: former "ExactMatch" is now only available
# as "RawKey"
elif search_key_name == 'ExactMatch':
search_key_name = 'RawKey'
# De-wrap value for parsing.
value = value['query']
# If necessary, re-wrap when building queries.
if len(raw_value) > 1:
def wrap(x):
result = raw_value.copy()
result['query'] = x
return result
else:
raw_value = value
else:
wrap = lambda x: x
search_key_name = None
search_key = self.getColumnDefaultSearchKey(key,
search_key_name=search_key_name)
......@@ -2093,7 +2102,7 @@ class Catalog(Folder,
)
else:
result = self.buildQueryFromAbstractSyntaxTreeNode(
abstract_syntax_tree, key, wrap,
abstract_syntax_tree, key, search_key_name, wrap,
ignore_unknown_columns=ignore_unknown_columns,
)
else:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment