From 4e1f92f5c989685ff952c0dafe17b4ab7b91058d Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Wed, 12 Feb 2020 20:37:56 -0600 Subject: [PATCH] - Enable editing all properties via WebDAV --- CHANGES.rst | 4 + .../ZSQLMethods/Extensions/TestRecord.py | 10 ++ src/Shared/DC/ZRDB/DA.py | 77 ++++++++-- src/Shared/DC/ZRDB/tests/testDA.py | 136 ++++++++++++++++++ 4 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 src/Products/ZSQLMethods/Extensions/TestRecord.py create mode 100644 src/Shared/DC/ZRDB/tests/testDA.py diff --git a/CHANGES.rst b/CHANGES.rst index d648ff4..07deb07 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,10 @@ Changelog 3.3 (unreleased) ---------------- +- Enable editing all properties via WebDAV + This is done by copying the full-featured format used by the + ``FSZSQLMethod`` class from ``Products.CMFCore``. + 3.2 (2020-02-11) ---------------- diff --git a/src/Products/ZSQLMethods/Extensions/TestRecord.py b/src/Products/ZSQLMethods/Extensions/TestRecord.py new file mode 100644 index 0000000..61c72da --- /dev/null +++ b/src/Products/ZSQLMethods/Extensions/TestRecord.py @@ -0,0 +1,10 @@ +# +# TestRecord - Used for ZSQLMethod tests +# + + +class MyRecord: + + def my_uncle(self): + """ Bruce is my uncle """ + return 'bruce' diff --git a/src/Shared/DC/ZRDB/DA.py b/src/Shared/DC/ZRDB/DA.py index 051e0d2..9c2f697 100644 --- a/src/Shared/DC/ZRDB/DA.py +++ b/src/Shared/DC/ZRDB/DA.py @@ -395,7 +395,16 @@ def manage_DAVget(self): """Get source for WebDAV""" self.REQUEST.RESPONSE.setHeader('Content-Type', self.default_content_type) - return '%s\n%s' % (self.arguments_src, self.src) + values = {} + for attr in ('title', 'connection_id', 'arguments_src', 'max_rows_', + 'max_cache_', 'cache_time_', 'class_name_', 'class_file_', + 'allow_simple_one_argument_traversal', 'src'): + values[attr] = getattr(self, attr) + values['connection_hook'] = self.connection_hook or '' + asoat = self.allow_simple_one_argument_traversal or '' + values['allow_simple_one_argument_traversal'] = asoat + + return DA_DAV_TEMPLATE % values manage_FTPget = manage_DAVget @@ -408,20 +417,53 @@ def PUT(self, REQUEST, RESPONSE): self.dav__init(REQUEST, RESPONSE) self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) body = REQUEST.get('BODY', '') + parameters = {} + connection_id = self.connection_id if six.PY3 and isinstance(body, bytes): body = body.decode('UTF-8') elif six.PY2 and not isinstance(body, bytes): body = body.encode('UTF-8') - m = re.match(r'\s*(.*)\s*\n', body, re.I | re.S) + + re_expr = r'\s*(.*)\s*\n' + m = re.match(re_expr, body, re.I | re.S) if m: - self.arguments_src = m.group(1) - self._arg = parse(self.arguments_src) + lines = [x for x in m.group(1).split('\n') if x] + for line in lines: + pair = line.split(':', 1) + if len(pair) != 2: + continue + parameters[pair[0].strip().lower()] = pair[1].strip() body = body[m.end():] - template = body - self.src = template - self.template = t = self.template_class(template) - t.cook() - self._v_cache = ({}, Bucket()) + + # check for required parameters + try: + connection_id = (parameters.get('connection id', '') or + parameters['connection_id']) + except KeyError as e: + raise ValueError('The "%s" parameter is required ' + 'but was not supplied' % e) + + self.manage_edit(parameters.get('title', ''), + connection_id, + parameters.get('arguments', ''), + template=body) + + connection_hook = parameters.get('connection_hook', None) + direct = (parameters.get('allow_simple_one_argument_traversal', + '') or '') + if direct: + if direct.lower() in ('0', 'false'): + direct = '' + else: + direct = True + self.manage_advanced(parameters.get('max_rows', 1000), + parameters.get('max_cache', 100), + parameters.get('cache_time', 0), + parameters.get('class_name', ''), + parameters.get('class_file', ''), + connection_hook=connection_hook, + direct=direct) + RESPONSE.setStatus(204) return RESPONSE @@ -781,3 +823,20 @@ def __getattr__(self, name): class SQLMethodTracebackSupplement: def __init__(self, sql): self.object = sql + + +DA_DAV_TEMPLATE = """\ + +title : %(title)s +connection id : %(connection_id)s +arguments : %(arguments_src)s +max_rows : %(max_rows_)s +max_cache : %(max_cache_)s +cache_time : %(cache_time_)s +class_name : %(class_name_)s +class_file : %(class_file_)s +connection_hook : %(connection_hook)s +allow_simple_one_argument_traversal : %(allow_simple_one_argument_traversal)s + +%(src)s +""" diff --git a/src/Shared/DC/ZRDB/tests/testDA.py b/src/Shared/DC/ZRDB/tests/testDA.py new file mode 100644 index 0000000..151eea0 --- /dev/null +++ b/src/Shared/DC/ZRDB/tests/testDA.py @@ -0,0 +1,136 @@ +############################################################################## +# +# Copyright (c) 2010 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +import unittest + +from Testing.makerequest import makerequest + + +class TestTM(unittest.TestCase): + + def _getTargetClass(self): + from ..DA import DA + return DA + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_instantiation(self): + da = self._makeOne('test_id', 'Test Title', 'conn_id', + 'foo bar', '') + self.assertEqual(da.getId(), 'test_id') + self.assertEqual(da.title, 'Test Title') + self.assertEqual(da.connection_id, 'conn_id') + self.assertEqual(da.arguments_src, 'foo bar') + self.assertEqual(da.src, '') + + def test_manage_edit(self): + da = self._makeOne('test_id', 'Test Title', 'conn_id', + 'foo bar', '') + da.manage_edit('New Title', 'conn_2', 'bar baz', '') + self.assertEqual(da.getId(), 'test_id') + self.assertEqual(da.title, 'New Title') + self.assertEqual(da.connection_id, 'conn_2') + self.assertEqual(da.arguments_src, 'bar baz') + self.assertEqual(da.src, '') + + def test_manage_advanced(self): + klass = self._getTargetClass() + da = self._makeOne('test_id', 'Test Title', 'conn_id', + 'foo bar', '') + + # Check defaults + self.assertEqual(da.max_rows_, klass.max_rows_) + self.assertEqual(da.max_cache_, klass.max_cache_) + self.assertEqual(da.cache_time_, klass.cache_time_) + self.assertEqual(da.class_name_, klass.class_name_) + self.assertEqual(da.class_file_, klass.class_file_) + self.assertEqual(da.allow_simple_one_argument_traversal, + klass.allow_simple_one_argument_traversal) + self.assertEqual(da.template_class, klass.template_class) + self.assertEqual(da.connection_hook, klass.connection_hook) + + da.manage_advanced(5, 50, 10, 'MyRecord', 'ZSQLMethods.TestRecord', + direct=True, connection_hook='foo') + self.assertEqual(da.max_rows_, 5) + self.assertEqual(da.max_cache_, 50) + self.assertEqual(da.cache_time_, 10) + self.assertEqual(da.class_name_, 'MyRecord') + self.assertEqual(da.class_file_, 'ZSQLMethods.TestRecord') + self.assertEqual(da.allow_simple_one_argument_traversal, True) + self.assertEqual(da.template_class, klass.template_class) + self.assertEqual(da.connection_hook, 'foo') + + def test_manage_DAVget(self): + da = makerequest(self._makeOne('test_id', 'Test Title', 'conn_id', + 'foo bar', '')) + self.assertEqual(da.manage_DAVget(), DEFAULT_DAV_SOURCE) + + da.manage_edit('New Title', 'conn_2', 'bar baz', '') + da.manage_advanced(5, 50, 10, 'MyRecord', 'ZSQLMethods.TestRecord', + direct=True, connection_hook='foo') + self.assertEqual(da.manage_DAVget(), CHANGED_DAV_SOURCE) + + def test_PUT(self): + klass = self._getTargetClass() + da = makerequest(self._makeOne('test_id', 'Test Title', 'conn_id', + 'foo bar', '')) + + da.REQUEST.set('BODY', CHANGED_DAV_SOURCE) + da.PUT(da.REQUEST, da.REQUEST.RESPONSE) + self.assertEqual(da.getId(), 'test_id') + self.assertEqual(da.title, 'New Title') + self.assertEqual(da.connection_id, 'conn_2') + self.assertEqual(da.arguments_src, 'bar baz') + self.assertEqual(da.src, '\n') + self.assertEqual(da.max_rows_, 5) + self.assertEqual(da.max_cache_, 50) + self.assertEqual(da.cache_time_, 10) + self.assertEqual(da.class_name_, 'MyRecord') + self.assertEqual(da.class_file_, 'ZSQLMethods.TestRecord') + self.assertEqual(da.allow_simple_one_argument_traversal, True) + self.assertEqual(da.template_class, klass.template_class) + self.assertEqual(da.connection_hook, 'foo') + + +DEFAULT_DAV_SOURCE = """\ + +title : Test Title +connection id : conn_id +arguments : foo bar +max_rows : 1000 +max_cache : 100 +cache_time : 0 +class_name : +class_file : +connection_hook : +allow_simple_one_argument_traversal : + + +""" # NOQA: W291 + +CHANGED_DAV_SOURCE = """\ + +title : New Title +connection id : conn_2 +arguments : bar baz +max_rows : 5 +max_cache : 50 +cache_time : 10 +class_name : MyRecord +class_file : ZSQLMethods.TestRecord +connection_hook : foo +allow_simple_one_argument_traversal : True + + +"""