diff --git a/last_commit.txt b/last_commit.txt index 66bce89036..3ef7a51f9b 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,270 +1,44 @@ -Repository: plone.resourceeditor +Repository: plone.app.caching Branch: refs/heads/master -Date: 2023-04-25T16:05:53+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/749d010f33bf3f388016efccfb382bbbcfa0572f +Date: 2023-04-25T23:19:25+02:00 +Author: Maurits van Rees (mauritsvanrees) +Commit: https://github.com/plone/plone.app.caching/commit/57e7947983065af8ce526015defa74def732b53d -Configuring with plone/meta +Update the resourceRegistries ETag to use the config registry modification time. -Files changed: -A .editorconfig -A .meta.toml -A .pre-commit-config.yaml -A news/2a4ba395.internal -A tox.ini -M pyproject.toml -M setup.cfg - -b'diff --git a/.editorconfig b/.editorconfig\nnew file mode 100644\nindex 0000000..b4158b8\n--- /dev/null\n+++ b/.editorconfig\n@@ -0,0 +1,39 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+#\n+# EditorConfig Configuration file, for more details see:\n+# http://EditorConfig.org\n+# EditorConfig is a convention description, that could be interpreted\n+# by multiple editors to enforce common coding conventions for specific\n+# file types\n+\n+# top-most EditorConfig file:\n+# Will ignore other EditorConfig files in Home directory or upper tree level.\n+root = true\n+\n+\n+[*] # For All Files\n+# Unix-style newlines with a newline ending every file\n+end_of_line = lf\n+insert_final_newline = true\n+trim_trailing_whitespace = true\n+# Set default charset\n+charset = utf-8\n+# Indent style default\n+indent_style = space\n+# Max Line Length - a hard line wrap, should be disabled\n+max_line_length = off\n+\n+[*.{py,cfg,ini}]\n+# 4 space indentation\n+indent_size = 4\n+\n+[*.{yml,zpt,pt,dtml,zcml}]\n+# 2 space indentation\n+indent_size = 2\n+\n+[{Makefile,.gitmodules}]\n+# Tab indentation (no size specified, but view as 4 spaces)\n+indent_style = tab\n+indent_size = unset\n+tab_width = unset\ndiff --git a/.meta.toml b/.meta.toml\nnew file mode 100644\nindex 0000000..99342b2\n--- /dev/null\n+++ b/.meta.toml\n@@ -0,0 +1,5 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[meta]\n+template = "default"\n+commit-id = "2a4ba395"\ndiff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml\nnew file mode 100644\nindex 0000000..fdafec1\n--- /dev/null\n+++ b/.pre-commit-config.yaml\n@@ -0,0 +1,42 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+ci:\n+ autofix_prs: false\n+ autoupdate_schedule: monthly\n+\n+repos:\n+- repo: https://github.com/asottile/pyupgrade\n+ rev: v3.3.1\n+ hooks:\n+ - id: pyupgrade\n+ args: [--py38-plus]\n+- repo: https://github.com/pycqa/isort\n+ rev: 5.12.0\n+ hooks:\n+ - id: isort\n+- repo: https://github.com/psf/black\n+ rev: 23.3.0\n+ hooks:\n+ - id: black\n+- repo: https://github.com/collective/zpretty\n+ rev: 3.0.3\n+ hooks:\n+ - id: zpretty\n+- repo: https://github.com/PyCQA/flake8\n+ rev: 6.0.0\n+ hooks:\n+ - id: flake8\n+- repo: https://github.com/codespell-project/codespell\n+ rev: v2.2.4\n+ hooks:\n+ - id: codespell\n+ additional_dependencies:\n+ - tomli\n+- repo: https://github.com/mgedmin/check-manifest\n+ rev: "0.49"\n+ hooks:\n+ - id: check-manifest\n+- repo: https://github.com/regebro/pyroma\n+ rev: "4.2"\n+ hooks:\n+ - id: pyroma\ndiff --git a/news/2a4ba395.internal b/news/2a4ba395.internal\nnew file mode 100644\nindex 0000000..c08f539\n--- /dev/null\n+++ b/news/2a4ba395.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone devs]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..639f77a 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -1,3 +1,5 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n [tool.towncrier]\n filename = "CHANGES.rst"\n directory = "news/"\n@@ -18,3 +20,65 @@ showcontent = true\n directory = "bugfix"\n name = "Bug fixes:"\n showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "internal"\n+name = "Internal:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "documentation"\n+name = "Documentation:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "tests"\n+name = "Tests"\n+showcontent = true\n+\n+[tool.isort]\n+profile = "plone"\n+\n+[tool.black]\n+target-version = ["py38"]\n+\n+\n+[tool.dependencychecker]\n+Zope = [\n+ # Zope own provided namespaces\n+ \'App\', \'OFS\', \'Products.Five\', \'Products.OFSP\', \'Products.PageTemplates\',\n+ \'Products.SiteAccess\', \'Shared\', \'Testing\', \'ZPublisher\', \'ZTUtils\',\n+ \'Zope2\', \'webdav\', \'zmi\',\n+ # ExtensionClass own provided namespaces\n+ \'ExtensionClass\', \'ComputedAttribute\', \'MethodObject\',\n+ # Zope dependencies\n+ \'AccessControl\', \'Acquisition\', \'AuthEncoding\', \'beautifulsoup4\', \'BTrees\',\n+ \'cffi\', \'Chameleon\', \'DateTime\', \'DocumentTemplate\',\n+ \'MultiMapping\', \'multipart\', \'PasteDeploy\', \'Persistence\', \'persistent\',\n+ \'pycparser\', \'python-gettext\', \'pytz\', \'RestrictedPython\', \'roman\',\n+ \'soupsieve\', \'transaction\', \'waitress\', \'WebOb\', \'WebTest\', \'WSGIProxy2\',\n+ \'z3c.pt\', \'zc.lockfile\', \'ZConfig\', \'zExceptions\', \'ZODB\', \'zodbpickle\',\n+ \'zope.annotation\', \'zope.browser\', \'zope.browsermenu\', \'zope.browserpage\',\n+ \'zope.browserresource\', \'zope.cachedescriptors\', \'zope.component\',\n+ \'zope.configuration\', \'zope.container\', \'zope.contentprovider\',\n+ \'zope.contenttype\', \'zope.datetime\', \'zope.deferredimport\',\n+ \'zope.deprecation\', \'zope.dottedname\', \'zope.event\', \'zope.exceptions\',\n+ \'zope.filerepresentation\', \'zope.globalrequest\', \'zope.hookable\',\n+ \'zope.i18n\', \'zope.i18nmessageid\', \'zope.interface\', \'zope.lifecycleevent\',\n+ \'zope.location\', \'zope.pagetemplate\', \'zope.processlifetime\', \'zope.proxy\',\n+ \'zope.ptresource\', \'zope.publisher\', \'zope.schema\', \'zope.security\',\n+ \'zope.sequencesort\', \'zope.site\', \'zope.size\', \'zope.structuredtext\',\n+ \'zope.tal\', \'zope.tales\', \'zope.testbrowser\', \'zope.testing\',\n+ \'zope.traversing\', \'zope.viewlet\'\n+]\n+\'Products.CMFCore\' = [\n+ \'docutils\', \'five.localsitemanager\', \'Missing\', \'Products.BTreeFolder2\',\n+ \'Products.GenericSetup\', \'Products.MailHost\', \'Products.PythonScripts\',\n+ \'Products.StandardCacheManagers\', \'Products.ZCatalog\', \'Record\',\n+ \'zope.sendmail\', \'Zope\'\n+]\n+\'plone.base\' = [\n+ \'plone.batching\', \'plone.registry\', \'plone.schema\',\'plone.z3cform\',\n+ \'Products.CMFCore\', \'Products.CMFDynamicViewFTI\',\n+]\n+python-dateutil = [\'dateutil\']\ndiff --git a/setup.cfg b/setup.cfg\nindex 2a9acf1..0da8f8f 100644\n--- a/setup.cfg\n+++ b/setup.cfg\n@@ -1,2 +1,23 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n [bdist_wheel]\n-universal = 1\n+universal = 0\n+\n+[flake8]\n+doctests = 1\n+ignore =\n+ # black takes care of line length\n+ E501,\n+ # black takes care of where to break lines\n+ W503,\n+ # black takes care of spaces within slicing (list[:])\n+ E203,\n+ # black takes care of spaces after commas\n+ E231,\n+\n+[check-manifest]\n+ignore =\n+ .editorconfig\n+ .meta.toml\n+ .pre-commit-config.yaml\n+ tox.ini\ndiff --git a/tox.ini b/tox.ini\nnew file mode 100644\nindex 0000000..fd8c81d\n--- /dev/null\n+++ b/tox.ini\n@@ -0,0 +1,76 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[tox]\n+# We need 4.4.0 for constrain_package_deps.\n+min_version = 4.4.0\n+envlist =\n+ format\n+ lint\n+ test\n+\n+[testenv]\n+allowlist_externals =\n+ sh\n+\n+[testenv:format]\n+description = automatically reformat code\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a pyupgrade\n+ pre-commit run -a isort\n+ pre-commit run -a black\n+ pre-commit run -a zpretty\n+\n+[testenv:lint]\n+description = run linters that will help improve the code style\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a\n+\n+[testenv:dependencies]\n+description = check if the package defines all its dependencies\n+skip_install = true\n+deps =\n+ build\n+ z3c.dependencychecker==2.11\n+commands =\n+ python -m build --sdist --no-isolation\n+ dependencychecker\n+\n+[testenv:dependencies-graph]\n+description = generate a graph out of the package\'s dependencies\n+deps =\n+ pipdeptree==2.5.1\n+ graphviz # optional dependency of pipdeptree\n+commands =\n+ sh -c \'pipdeptree --exclude setuptools,wheel,pipdeptree,zope.interface,zope.component --graph-output svg > dependencies.svg\'\n+\n+[testenv:test]\n+use_develop = true\n+constrain_package_deps = true\n+set_env = ROBOT_BROWSER=headlesschrome\n+deps =\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+commands =\n+ zope-testrunner --all --test-path={toxinidir} -s plone.resourceeditor {posargs}\n+extras =\n+ test\n+\n+[testenv:coverage]\n+use_develop = true\n+constrain_package_deps = true\n+set_env = ROBOT_BROWSER=headlesschrome\n+deps =\n+ coverage\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+commands =\n+ coverage run {envbindir}/zope-testrunner --all --test-path={toxinidir} -s plone.resourceeditor {posargs}\n+ coverage report -m --format markdown\n+extras =\n+ test\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:07:13+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/a7a6f1b1811f5b56e25c2f0f6669fe0557984260 - -chore: pyupgrade - -Files changed: -M plone/__init__.py -M plone/resourceeditor/__init__.py -M plone/resourceeditor/browser.py -M plone/resourceeditor/testing.py -M plone/resourceeditor/tests/test_file_manager.py -M plone/resourceeditor/tests/test_file_manager_action.py - -b'diff --git a/plone/__init__.py b/plone/__init__.py\nindex 68c04af..de40ea7 100644\n--- a/plone/__init__.py\n+++ b/plone/__init__.py\n@@ -1,2 +1 @@\n-# -*- coding: utf-8 -*-\n __import__(\'pkg_resources\').declare_namespace(__name__)\ndiff --git a/plone/resourceeditor/__init__.py b/plone/resourceeditor/__init__.py\nindex a4e0266..cce069d 100644\n--- a/plone/resourceeditor/__init__.py\n+++ b/plone/resourceeditor/__init__.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n import mimetypes\n import os.path\n \ndiff --git a/plone/resourceeditor/browser.py b/plone/resourceeditor/browser.py\nindex 9d6a0fe..6f2670e 100644\n--- a/plone/resourceeditor/browser.py\n+++ b/plone/resourceeditor/browser.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from AccessControl import Unauthorized\n from DateTime import DateTime\n from OFS.Image import File\n@@ -11,7 +10,7 @@\n from Products.CMFPlone.utils import safe_encode\n from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\n from six.moves import urllib\n-from six.moves.urllib.parse import urlparse\n+from urllib.parse import urlparse\n from time import localtime\n from time import strftime\n from zExceptions import NotFound\n@@ -29,12 +28,12 @@\n import six\n \n \n-_ = MessageFactory(u\'plone\')\n+_ = MessageFactory(\'plone\')\n \n \n def authorize(context, request):\n authenticator = queryMultiAdapter((context, request),\n- name=u\'authenticator\')\n+ name=\'authenticator\')\n if authenticator is not None and not authenticator.verify():\n raise Unauthorized\n \n@@ -86,7 +85,7 @@ def getFolder(self, path):\n to getFolder(). This can be used for example to only show image files\n in a file system tree.\n """\n- if six.PY2 and isinstance(path, six.text_type):\n+ if six.PY2 and isinstance(path, str):\n path = safe_encode(path, \'utf-8\')\n \n folders = []\n@@ -99,12 +98,12 @@ def getFolder(self, path):\n if IResourceDirectory.providedBy(folder[name]):\n folders.append(self.getInfo(\n folder[name],\n- path=\'/{0}/{1}/\'.format(path, name)\n+ path=f\'/{path}/{name}/\'\n ))\n else:\n files.append(self.getInfo(\n folder[name],\n- path=\'/{0}/{1}\'.format(path, name)\n+ path=f\'/{path}/{name}\'\n ))\n return folders + files\n \n@@ -126,7 +125,7 @@ def getFile(self, path):\n try:\n data = self.context.readFile(path)\n \n- if six.PY2 and isinstance(data, six.text_type):\n+ if six.PY2 and isinstance(data, str):\n result[\'contents\'] = data.encode(\'utf8\')\n else:\n result[\'contents\'] = safe_unicode(data)\n@@ -192,13 +191,13 @@ def getInfo(self, obj, path=\'/\'):\n size = stats.st_size / 1024\n \n if size < 1024:\n- size_specifier = u\'kb\'\n+ size_specifier = \'kb\'\n else:\n- size_specifier = u\'mb\'\n+ size_specifier = \'mb\'\n size = size / 1024\n- properties[\'size\'] = \'{0}{1}\'.format(\n+ properties[\'size\'] = \'{}{}\'.format(\n size,\n- translate(_(u\'filemanager_{0}\'.format(size_specifier),\n+ translate(_(f\'filemanager_{size_specifier}\',\n default=size_specifier), context=self.request)\n )\n \n@@ -274,19 +273,19 @@ def addFolder(self, path, name):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'),\n+ error = translate(_(\'filemanager_invalid_foldername\',\n+ default=\'Invalid folder name.\'),\n context=self.request)\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Folder already exists.\'),\n+ error = translate(_(\'filemanager_error_folder_exists\',\n+ default=\'Folder already exists.\'),\n context=self.request)\n code = 1\n else:\n@@ -295,8 +294,8 @@ def addFolder(self, path, name):\n except UnicodeDecodeError:\n error = translate(\n _(\n- u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'\n+ \'filemanager_invalid_foldername\',\n+ default=\'Invalid folder name.\'\n ),\n context=self.request\n )\n@@ -321,24 +320,24 @@ def addFile(self, path, name):\n code = 0\n \n parentPath = self.normalizePath(path)\n- newPath = \'{0}/{1}\'.format(parentPath, name,)\n+ newPath = f\'{parentPath}/{name}\'\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_filename\',\n- default=u\'Invalid file name.\'),\n+ error = translate(_(\'filemanager_invalid_filename\',\n+ default=\'Invalid file name.\'),\n context=self.request)\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n+ error = translate(_(\'filemanager_error_file_exists\',\n+ default=\'File already exists.\'),\n context=self.request)\n code = 1\n else:\n@@ -368,16 +367,16 @@ def delete(self, path):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n try:\n del parent[name]\n except KeyError:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n+ error = translate(_(\'filemanager_error_file_not_found\',\n+ default=\'File not found.\'),\n context=self.request)\n code = 1\n \n@@ -406,8 +405,8 @@ def renameFile(self, path, newName):\n try:\n parent = self.getObject(oldPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n@@ -415,8 +414,8 @@ def renameFile(self, path, newName):\n if newName in parent:\n error = translate(\n _(\n- u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'\n+ \'filemanager_error_file_exists\',\n+ default=\'File already exists.\'\n ),\n context=self.request)\n code = 1\n@@ -448,13 +447,13 @@ def move(self, path, directory):\n \n code = 0\n error = \'\'\n- newCanonicalPath = \'{0}/{1}\'.format(newParentPath, filename)\n+ newCanonicalPath = f\'{newParentPath}/{filename}\'\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n return json.dumps({\n@@ -466,8 +465,8 @@ def move(self, path, directory):\n try:\n target = self.getObject(newParentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Destination folder not found.\'),\n+ error = translate(_(\'filemanager_error_folder_exists\',\n+ default=\'Destination folder not found.\'),\n context=self.request)\n code = 1\n return json.dumps({\n@@ -477,13 +476,13 @@ def move(self, path, directory):\n })\n \n if filename not in parent:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n+ error = translate(_(\'filemanager_error_file_not_found\',\n+ default=\'File not found.\'),\n context=self.request)\n code = 1\n elif filename in target:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n+ error = translate(_(\'filemanager_error_file_exists\',\n+ default=\'File already exists.\'),\n context=self.request)\n code = 1\n else:\n@@ -571,7 +570,7 @@ def download(self, path):\n \'application/octet-stream\')\n self.request.response.setHeader(\n \'Content-Disposition\',\n- \'attachment; filename="{0}"\'.format(name)\n+ f\'attachment; filename="{name}"\'\n )\n \n # TODO: Use streams here if we can\n@@ -609,7 +608,7 @@ def pattern_options(self):\n site = getSite()\n viewName = \'@@plone.resourceeditor.filemanager-actions\'\n return json.dumps({\n- \'actionUrl\': \'{0}/++{1}++{2}/{3}\'.format(\n+ \'actionUrl\': \'{}/++{}++{}/{}\'.format(\n site.absolute_url(),\n self.context.__parent__.__parent__.__name__,\n self.context.__name__,\n@@ -627,39 +626,39 @@ def mode_selector(self, form):\n response = {\'error:\': \'Unknown request\', \'code\': -1}\n textareaWrap = False\n \n- if mode == u\'getfolder\':\n+ if mode == \'getfolder\':\n response = self.getFolder(\n path=urllib.parse.unquote(form[\'path\']),\n getSizes=form.get(\'getsizes\', \'false\') == \'true\'\n )\n- elif mode == u\'getinfo\':\n+ elif mode == \'getinfo\':\n response = self.getInfo(\n path=urllib.parse.unquote(form[\'path\']),\n getSize=form.get(\'getsize\', \'false\') == \'true\'\n )\n- elif mode == u\'addfolder\':\n+ elif mode == \'addfolder\':\n response = self.addFolder(\n path=urllib.parse.unquote(form[\'path\']),\n name=urllib.parse.unquote(form[\'name\'])\n )\n- elif mode == u\'add\':\n+ elif mode == \'add\':\n textareaWrap = True\n response = self.add(\n path=urllib.parse.unquote(form[\'currentpath\']),\n newfile=form[\'newfile\'],\n replacepath=form.get(\'replacepath\', None)\n )\n- elif mode == u\'addnew\':\n+ elif mode == \'addnew\':\n response = self.addNew(\n path=urllib.parse.unquote(form[\'path\']),\n name=urllib.parse.unquote(form[\'name\'])\n )\n- elif mode == u\'rename\':\n+ elif mode == \'rename\':\n response = self.rename(\n path=urllib.parse.unquote(form[\'old\']),\n newName=urllib.parse.unquote(form[\'new\'])\n )\n- elif mode == u\'delete\':\n+ elif mode == \'delete\':\n response = self.delete(\n path=urllib.parse.unquote(form[\'path\'])\n )\n@@ -668,13 +667,13 @@ def mode_selector(self, form):\n path=urllib.parse.unquote(form[\'path\']),\n directory=urllib.parse.unquote(form[\'directory\'])\n )\n- elif mode == u\'download\':\n+ elif mode == \'download\':\n return self.download(\n path=urllib.parse.unquote(form[\'path\'])\n )\n if textareaWrap:\n self.request.response.setHeader(\'Content-Type\', \'text/html\')\n- return \'\'.format(json.dumps(response))\n+ return f\'\'\n self.request.response.setHeader(\'Content-Type\',\n \'application/json\')\n return json.dumps(response)\n@@ -712,7 +711,7 @@ def resourceType(self):\n \n @zproperty.Lazy\n def baseUrl(self):\n- return \'{0}/++{1}++{2}\'.format(\n+ return \'{}/++{}++{}\'.format(\n self.portalUrl,\n self.resourceType,\n self.resourceDirectory.__name__\n@@ -720,16 +719,16 @@ def baseUrl(self):\n \n @zproperty.Lazy\n def fileConnector(self):\n- return \'{0}/@@{1}\'.format(self.baseUrl, self.__name__,)\n+ return f\'{self.baseUrl}/@@{self.__name__}\'\n \n @zproperty.Lazy\n def filemanagerConfiguration(self):\n return """\\\n var FILE_ROOT = \'/\';\n-var IMAGES_EXT = {0};\n-var CAPABILITIES = {1};\n-var FILE_CONNECTOR = \'{2}\';\n-var BASE_URL = \'{3}\';\n+var IMAGES_EXT = {};\n+var CAPABILITIES = {};\n+var FILE_CONNECTOR = \'{}\';\n+var BASE_URL = \'{}\';\n """.format(\n repr(self.imageExtensions),\n repr(self.capabilities),\n@@ -783,10 +782,10 @@ def getFolder(self, path, getSizes=False):\n for name in folder.listDirectory():\n if IResourceDirectory.providedBy(folder[name]):\n folders.append(self.getInfo(\n- path=\'{0}/{1}/\'.format(path, name), getSize=getSizes))\n+ path=f\'{path}/{name}/\', getSize=getSizes))\n else:\n files.append(self.getInfo(\n- path=\'{0}/{1}\'.format(path, name), getSize=getSizes))\n+ path=f\'{path}/{name}\', getSize=getSizes))\n return folders + files\n \n def getInfo(self, path, getSize=False):\n@@ -814,13 +813,13 @@ def getInfo(self, path, getSize=False):\n properties[\'dateModified\'] = DateTime(obj._p_mtime).strftime(\'%c\')\n size = obj.get_size() / 1024\n if size < 1024:\n- size_specifier = u\'kb\'\n+ size_specifier = \'kb\'\n else:\n- size_specifier = u\'mb\'\n+ size_specifier = \'mb\'\n size = size / 1024\n- properties[\'size\'] = \'{0}{1}\'.format(\n+ properties[\'size\'] = \'{}{}\'.format(\n size,\n- translate(_(u\'filemanager_{0}\'.format(size_specifier),\n+ translate(_(f\'filemanager_{size_specifier}\',\n default=size_specifier), context=self.request)\n )\n \n@@ -829,13 +828,13 @@ def getInfo(self, path, getSize=False):\n siteUrl = self.portalUrl\n resourceName = self.resourceDirectory.__name__\n \n- preview = \'{0}/{1}/images/fileicons/default.png\'.format(\n+ preview = \'{}/{}/images/fileicons/default.png\'.format(\n siteUrl,\n self.staticFiles\n )\n \n if IResourceDirectory.providedBy(obj):\n- preview = \'{0}/{1}/images/fileicons/_Open.png\'.format(\n+ preview = \'{}/{}/images/fileicons/_Open.png\'.format(\n siteUrl,\n self.staticFiles\n )\n@@ -844,14 +843,14 @@ def getInfo(self, path, getSize=False):\n else:\n fileType = self.getExtension(path, obj)\n if fileType in self.imageExtensions:\n- preview = \'{0}/++{1}++{2}/{3}\'.format(\n+ preview = \'{}/++{}++{}/{}\'.format(\n siteUrl,\n self.resourceType,\n resourceName,\n path\n )\n elif fileType in self.extensionsWithIcons:\n- preview = \'{0}/{1}/images/fileicons/{2}.png\'.format(\n+ preview = \'{}/{}/images/fileicons/{}.png\'.format(\n siteUrl,\n self.staticFiles,\n fileType\n@@ -887,19 +886,19 @@ def addFolder(self, path, name):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'),\n+ error = translate(_(\'filemanager_invalid_foldername\',\n+ default=\'Invalid folder name.\'),\n context=self.request)\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Folder already exists.\'),\n+ error = translate(_(\'filemanager_error_folder_exists\',\n+ default=\'Folder already exists.\'),\n context=self.request)\n code = 1\n else:\n@@ -908,8 +907,8 @@ def addFolder(self, path, name):\n except UnicodeDecodeError:\n error = translate(\n _(\n- u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'\n+ \'filemanager_invalid_foldername\',\n+ default=\'Invalid folder name.\'\n ),\n context=self.request\n )\n@@ -944,34 +943,34 @@ def add(self, path, newfile, replacepath=None):\n code = 0\n \n name = newfile.filename\n- if six.PY2 and isinstance(name, six.text_type):\n+ if six.PY2 and isinstance(name, str):\n name = safe_encode(name, \'utf-8\')\n \n if replacepath:\n newPath = replacepath\n parentPath = \'/\'.join(replacepath.split(\'/\')[:-1])\n else:\n- newPath = \'{0}/{1}\'.format(parentPath, name,)\n+ newPath = f\'{parentPath}/{name}\'\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n if name in parent and not replacepath:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n+ error = translate(_(\'filemanager_error_file_exists\',\n+ default=\'File already exists.\'),\n context=self.request)\n code = 1\n else:\n try:\n self.resourceDirectory.writeFile(newPath, newfile)\n except ValueError:\n- error = translate(_(u\'filemanager_error_file_invalid\',\n- default=u\'Could not read file.\'),\n+ error = translate(_(\'filemanager_error_file_invalid\',\n+ default=\'Could not read file.\'),\n context=self.request)\n code = 1\n \n@@ -994,24 +993,24 @@ def addNew(self, path, name):\n code = 0\n \n parentPath = self.normalizePath(path)\n- newPath = \'{0}/{1}\'.format(parentPath, name,)\n+ newPath = f\'{parentPath}/{name}\'\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_filename\',\n- default=u\'Invalid file name.\'),\n+ error = translate(_(\'filemanager_invalid_filename\',\n+ default=\'Invalid file name.\'),\n context=self.request)\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n+ error = translate(_(\'filemanager_error_file_exists\',\n+ default=\'File already exists.\'),\n context=self.request)\n code = 1\n else:\n@@ -1041,16 +1040,16 @@ def rename(self, path, newName):\n try:\n parent = self.getObject(oldPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n if newName != oldName:\n if newName in parent:\n error = translate(\n- _(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n+ _(\'filemanager_error_file_exists\',\n+ default=\'File already exists.\'),\n context=self.request)\n code = 1\n else:\n@@ -1080,16 +1079,16 @@ def delete(self, path):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n else:\n try:\n del parent[name]\n except KeyError:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n+ error = translate(_(\'filemanager_error_file_not_found\',\n+ default=\'File not found.\'),\n context=self.request)\n code = 1\n \n@@ -1114,13 +1113,13 @@ def move(self, path, directory):\n \n code = 0\n error = \'\'\n- newCanonicalPath = \'{0}/{1}\'.format(newParentPath, filename)\n+ newCanonicalPath = f\'{newParentPath}/{filename}\'\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n+ error = translate(_(\'filemanager_invalid_parent\',\n+ default=\'Parent folder not found.\'),\n context=self.request)\n code = 1\n return {\n@@ -1132,8 +1131,8 @@ def move(self, path, directory):\n try:\n target = self.getObject(newParentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Destination folder not found.\'),\n+ error = translate(_(\'filemanager_error_folder_exists\',\n+ default=\'Destination folder not found.\'),\n context=self.request)\n code = 1\n return {\n@@ -1143,13 +1142,13 @@ def move(self, path, directory):\n }\n \n if filename not in parent:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n+ error = translate(_(\'filemanager_error_file_not_found\',\n+ default=\'File not found.\'),\n context=self.request)\n code = 1\n elif filename in target:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n+ error = translate(_(\'filemanager_error_file_exists\',\n+ default=\'File already exists.\'),\n context=self.request)\n code = 1\n else:\n@@ -1179,7 +1178,7 @@ def download(self, path):\n \'application/octet-stream\')\n self.request.response.setHeader(\n \'Content-Disposition\',\n- \'attachment; filename="{0}"\'.format(name)\n+ f\'attachment; filename="{name}"\'\n )\n \n # TODO: Use streams here if we can\n@@ -1245,7 +1244,7 @@ def filetree(self):\n def getFolder(root, relpath=\'\'):\n result = []\n for name in root.listDirectory():\n- path = \'{0}/{1}\'.format(relpath, name)\n+ path = f\'{relpath}/{name}\'\n if IResourceDirectory.providedBy(root[name]):\n item = {\n \'title\': name,\ndiff --git a/plone/resourceeditor/testing.py b/plone/resourceeditor/testing.py\nindex de5cbe1..10ada31 100644\n--- a/plone/resourceeditor/testing.py\n+++ b/plone/resourceeditor/testing.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.app.testing import applyProfile\n from plone.app.testing import IntegrationTesting\n from plone.app.testing import PLONE_FIXTURE\ndiff --git a/plone/resourceeditor/tests/test_file_manager.py b/plone/resourceeditor/tests/test_file_manager.py\nindex c3312e5..8310986 100644\n--- a/plone/resourceeditor/tests/test_file_manager.py\n+++ b/plone/resourceeditor/tests/test_file_manager.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING # noqa\n \n import six\n@@ -124,7 +123,7 @@ def test_addfolder_invalid_parent(self):\n \n def test_add(self):\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+ from io import BytesIO\n r = self._make_directory()\n \n view = FileManager(r, self.layer[\'request\'])\n@@ -141,7 +140,7 @@ def test_add(self):\n \n def test_add_subfolder(self):\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+ from io import BytesIO\n r = self._make_directory()\n r.makeDirectory(\'alpha\')\n \n@@ -160,7 +159,7 @@ def test_add_subfolder(self):\n \n def test_add_exists(self):\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+ from io import BytesIO\n r = self._make_directory()\n r.writeFile(\'test.txt\', b\'boo\')\n \n@@ -178,7 +177,7 @@ def test_add_exists(self):\n \n def test_add_replace(self):\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+ from io import BytesIO\n r = self._make_directory()\n r.writeFile(\'test.txt\', b\'boo\')\n \ndiff --git a/plone/resourceeditor/tests/test_file_manager_action.py b/plone/resourceeditor/tests/test_file_manager_action.py\nindex b724976..cd594e3 100644\n--- a/plone/resourceeditor/tests/test_file_manager_action.py\n+++ b/plone/resourceeditor/tests/test_file_manager_action.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING # noqa\n import json\n import unittest\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:08:41+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/7fe428b24a7b4f4eb4a3161f33acc7530cc46870 - -chore: isort - -Files changed: -M plone/resourceeditor/browser.py -M plone/resourceeditor/tests/test_file_manager.py -M plone/resourceeditor/tests/test_file_manager_action.py -M setup.py - -b"diff --git a/plone/resourceeditor/browser.py b/plone/resourceeditor/browser.py\nindex 6f2670e..7c94517 100644\n--- a/plone/resourceeditor/browser.py\n+++ b/plone/resourceeditor/browser.py\n@@ -6,13 +6,13 @@\n from plone.resource.file import FilesystemFile\n from plone.resource.interfaces import IResourceDirectory\n from Products.CMFCore.utils import getToolByName\n-from Products.CMFPlone.utils import safe_unicode\n from Products.CMFPlone.utils import safe_encode\n+from Products.CMFPlone.utils import safe_unicode\n from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\n from six.moves import urllib\n-from urllib.parse import urlparse\n from time import localtime\n from time import strftime\n+from urllib.parse import urlparse\n from zExceptions import NotFound\n from zope.cachedescriptors import property as zproperty\n from zope.component import queryMultiAdapter\ndiff --git a/plone/resourceeditor/tests/test_file_manager.py b/plone/resourceeditor/tests/test_file_manager.py\nindex 8310986..d0049ec 100644\n--- a/plone/resourceeditor/tests/test_file_manager.py\n+++ b/plone/resourceeditor/tests/test_file_manager.py\n@@ -1,4 +1,4 @@\n-from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING # noqa\n+from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n \n import six\n import unittest\n@@ -122,8 +122,8 @@ def test_addfolder_invalid_parent(self):\n self.assertEqual(info['name'], 'beta')\n \n def test_add(self):\n- from plone.resourceeditor.browser import FileManager\n from io import BytesIO\n+ from plone.resourceeditor.browser import FileManager\n r = self._make_directory()\n \n view = FileManager(r, self.layer['request'])\n@@ -139,8 +139,8 @@ def test_add(self):\n self.assertEqual(info['parent'], '/')\n \n def test_add_subfolder(self):\n- from plone.resourceeditor.browser import FileManager\n from io import BytesIO\n+ from plone.resourceeditor.browser import FileManager\n r = self._make_directory()\n r.makeDirectory('alpha')\n \n@@ -158,8 +158,8 @@ def test_add_subfolder(self):\n self.assertEqual(info['parent'], '/alpha')\n \n def test_add_exists(self):\n- from plone.resourceeditor.browser import FileManager\n from io import BytesIO\n+ from plone.resourceeditor.browser import FileManager\n r = self._make_directory()\n r.writeFile('test.txt', b'boo')\n \n@@ -176,8 +176,8 @@ def test_add_exists(self):\n self.assertEqual(r.readFile('test.txt'), b'boo')\n \n def test_add_replace(self):\n- from plone.resourceeditor.browser import FileManager\n from io import BytesIO\n+ from plone.resourceeditor.browser import FileManager\n r = self._make_directory()\n r.writeFile('test.txt', b'boo')\n \ndiff --git a/plone/resourceeditor/tests/test_file_manager_action.py b/plone/resourceeditor/tests/test_file_manager_action.py\nindex cd594e3..3f999a2 100644\n--- a/plone/resourceeditor/tests/test_file_manager_action.py\n+++ b/plone/resourceeditor/tests/test_file_manager_action.py\n@@ -1,4 +1,5 @@\n-from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING # noqa\n+from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n+\n import json\n import unittest\n \ndiff --git a/setup.py b/setup.py\nindex bebd322..8b4f192 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -1,4 +1,6 @@\n-from setuptools import setup, find_packages\n+from setuptools import find_packages\n+from setuptools import setup\n+\n \n version = '3.0.5.dev0'\n \n" - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:10:02+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/e5ff3f701a7263096720a775a4a131fe637097d7 - -chore: black - -Files changed: -M plone/__init__.py -M plone/resourceeditor/__init__.py -M plone/resourceeditor/browser.py -M plone/resourceeditor/testing.py -M plone/resourceeditor/tests/test_file_manager.py -M plone/resourceeditor/tests/test_file_manager_action.py -M setup.py - -b'diff --git a/plone/__init__.py b/plone/__init__.py\nindex de40ea7..5284146 100644\n--- a/plone/__init__.py\n+++ b/plone/__init__.py\n@@ -1 +1 @@\n-__import__(\'pkg_resources\').declare_namespace(__name__)\n+__import__("pkg_resources").declare_namespace(__name__)\ndiff --git a/plone/resourceeditor/__init__.py b/plone/resourceeditor/__init__.py\nindex cce069d..3447c9b 100644\n--- a/plone/resourceeditor/__init__.py\n+++ b/plone/resourceeditor/__init__.py\n@@ -17,4 +17,4 @@ def add_files(filenames):\n \n \n here = os.path.dirname(os.path.abspath(__file__))\n-add_files([os.path.join(here, \'mime.types\')])\n+add_files([os.path.join(here, "mime.types")])\ndiff --git a/plone/resourceeditor/browser.py b/plone/resourceeditor/browser.py\nindex 7c94517..9d6366e 100644\n--- a/plone/resourceeditor/browser.py\n+++ b/plone/resourceeditor/browser.py\n@@ -28,12 +28,11 @@\n import six\n \n \n-_ = MessageFactory(\'plone\')\n+_ = MessageFactory("plone")\n \n \n def authorize(context, request):\n- authenticator = queryMultiAdapter((context, request),\n- name=\'authenticator\')\n+ authenticator = queryMultiAdapter((context, request), name="authenticator")\n if authenticator is not None and not authenticator.verify():\n raise Unauthorized\n \n@@ -46,9 +45,8 @@ def validateFilename(name):\n \n \n class FileManagerActions(BrowserView):\n-\n- imageExtensions = [\'png\', \'gif\', \'jpg\', \'jpeg\', \'ico\']\n- previewTemplate = ViewPageTemplateFile(\'preview.pt\')\n+ imageExtensions = ["png", "gif", "jpg", "jpeg", "ico"]\n+ previewTemplate = ViewPageTemplateFile("preview.pt")\n \n @zproperty.Lazy\n def resourceDirectory(self):\n@@ -60,7 +58,7 @@ def getObject(self, path):\n return self.resourceDirectory\n try:\n return self.resourceDirectory[path]\n- except (KeyError, NotFound,):\n+ except (KeyError, NotFound):\n raise KeyError(path)\n \n def getExtension(self, obj=None, path=None):\n@@ -86,7 +84,7 @@ def getFolder(self, path):\n in a file system tree.\n """\n if six.PY2 and isinstance(path, str):\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n folders = []\n files = []\n@@ -96,70 +94,64 @@ def getFolder(self, path):\n \n for name in folder.listDirectory():\n if IResourceDirectory.providedBy(folder[name]):\n- folders.append(self.getInfo(\n- folder[name],\n- path=f\'/{path}/{name}/\'\n- ))\n+ folders.append(self.getInfo(folder[name], path=f"/{path}/{name}/"))\n else:\n- files.append(self.getInfo(\n- folder[name],\n- path=f\'/{path}/{name}\'\n- ))\n+ files.append(self.getInfo(folder[name], path=f"/{path}/{name}"))\n return folders + files\n \n def getFile(self, path):\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n path = self.normalizePath(path)\n ext = self.getExtension(path=path)\n- result = {\'ext\': ext}\n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n+ result = {"ext": ext}\n+ self.request.response.setHeader("Content-Type", "application/json")\n \n if ext in self.imageExtensions:\n obj = self.getObject(path)\n info = self.getInfo(obj)\n- info[\'preview\'] = path\n- result[\'info\'] = self.previewTemplate(info=info)\n+ info["preview"] = path\n+ result["info"] = self.previewTemplate(info=info)\n return json.dumps(result)\n else:\n try:\n data = self.context.readFile(path)\n \n if six.PY2 and isinstance(data, str):\n- result[\'contents\'] = data.encode(\'utf8\')\n+ result["contents"] = data.encode("utf8")\n else:\n- result[\'contents\'] = safe_unicode(data)\n+ result["contents"] = safe_unicode(data)\n try:\n return json.dumps(result)\n except UnicodeDecodeError:\n # The file we\'re trying to get isn\'t unicode encodable\n # so we just return the file information, not the content\n- del result[\'contents\']\n+ del result["contents"]\n obj = self.getObject(path)\n info = self.getInfo(obj)\n- result[\'info\'] = self.previewTemplate(info=info)\n+ result["info"] = self.previewTemplate(info=info)\n return json.dumps(result)\n except AttributeError:\n return None\n \n def normalizePath(self, path):\n- if path.startswith(\'/\'):\n+ if path.startswith("/"):\n path = path[1:]\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n return path\n \n def normalizeReturnPath(self, path):\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n- if not path.startswith(\'/\'):\n- path = \'/\' + path\n+ if not path.startswith("/"):\n+ path = "/" + path\n return path\n \n def parentPath(self, path):\n- return \'/\'.join(path.split(\'/\')[:-1])\n+ return "/".join(path.split("/")[:-1])\n \n- def getInfo(self, obj, path=\'/\'):\n+ def getInfo(self, obj, path="/"):\n """Returns information about a single file. Requests\n with mode "getinfo" will include an additional parameter, "path",\n indicating which file to inspect. A boolean parameter "getsize"\n@@ -169,17 +161,17 @@ def getInfo(self, obj, path=\'/\'):\n filename = obj.__name__\n \n properties = {\n- \'dateModified\': None,\n+ "dateModified": None,\n }\n \n size = 0\n \n if isinstance(obj, File):\n- properties[\'dateModified\'] = DateTime(obj._p_mtime).strftime(\'%c\')\n+ properties["dateModified"] = DateTime(obj._p_mtime).strftime("%c")\n size = obj.get_size() / 1024\n \n if IResourceDirectory.providedBy(obj):\n- fileType = \'dir\'\n+ fileType = "dir"\n is_folder = True\n else:\n fileType = self.getExtension(obj)\n@@ -187,55 +179,57 @@ def getInfo(self, obj, path=\'/\'):\n if isinstance(obj, FilesystemFile):\n stats = os.stat(obj.path)\n modified = localtime(stats.st_mtime)\n- properties[\'dateModified\'] = strftime(\'%c\', modified)\n+ properties["dateModified"] = strftime("%c", modified)\n size = stats.st_size / 1024\n \n if size < 1024:\n- size_specifier = \'kb\'\n+ size_specifier = "kb"\n else:\n- size_specifier = \'mb\'\n+ size_specifier = "mb"\n size = size / 1024\n- properties[\'size\'] = \'{}{}\'.format(\n+ properties["size"] = "{}{}".format(\n size,\n- translate(_(f\'filemanager_{size_specifier}\',\n- default=size_specifier), context=self.request)\n+ translate(\n+ _(f"filemanager_{size_specifier}", default=size_specifier),\n+ context=self.request,\n+ ),\n )\n \n if isinstance(obj, Image):\n- properties[\'height\'] = obj.height\n- properties[\'width\'] = obj.width\n+ properties["height"] = obj.height\n+ properties["width"] = obj.width\n \n return {\n- \'filename\': filename,\n- \'label\': filename,\n- \'fileType\': fileType,\n- \'filesystem\': isinstance(obj, FilesystemFile),\n- \'properties\': properties,\n- \'path\': path,\n- \'folder\': is_folder\n+ "filename": filename,\n+ "label": filename,\n+ "fileType": fileType,\n+ "filesystem": isinstance(obj, FilesystemFile),\n+ "properties": properties,\n+ "path": path,\n+ "folder": is_folder,\n }\n \n def saveFile(self, path, value):\n- path = path.lstrip(\'/\')\n+ path = path.lstrip("/")\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n value = value.strip()\n if six.PY2:\n- value = safe_encode(value, \'utf-8\')\n+ value = safe_encode(value, "utf-8")\n \n- value = value.replace(\'\\r\\n\', \'\\n\')\n+ value = value.replace("\\r\\n", "\\n")\n \n if path in self.context:\n if IResourceDirectory.providedBy(self.context[path]):\n- return json.dumps({\'error\': \'invalid path\'})\n+ return json.dumps({"error": "invalid path"})\n \n- if \'relativeUrls\' in self.request.form:\n- reg = re.compile(r\'url\\(([^)]+)\\)\')\n+ if "relativeUrls" in self.request.form:\n+ reg = re.compile(r"url\\(([^)]+)\\)")\n urls = reg.findall(value)\n \n # Trim off the @@plone.resourceeditor bit to just give us the\n # theme url\n- limit = self.request.URL.find(\'@@plone.resourceeditor\')\n+ limit = self.request.URL.find("@@plone.resourceeditor")\n location = self.request.URL[0:limit]\n base = urlparse(location)\n for url in urls:\n@@ -243,29 +237,28 @@ def saveFile(self, path, value):\n if base.netloc != asset.netloc:\n continue\n \n- base_dir = \'.\' + posixpath.dirname(base.path)\n- target = \'.\' + asset.path\n+ base_dir = "." + posixpath.dirname(base.path)\n+ target = "." + asset.path\n out = posixpath.relpath(target, start=base_dir)\n value = value.replace(url.strip(\'"\').strip("\'"), out)\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n+ self.request.response.setHeader("Content-Type", "application/json")\n if isinstance(self.context, FilesystemResourceDirectory):\n # we cannot save in an FS directory, but we return the file content\n # (useful when we compile less from the theming editor)\n- return json.dumps({\'success\': \'tmp\', \'value\': value})\n+ return json.dumps({"success": "tmp", "value": value})\n else:\n self.context.writeFile(path, value)\n- return json.dumps({\'success\': \'save\'})\n+ return json.dumps({"success": "save"})\n \n def addFolder(self, path, name):\n- """Create a new directory on the server within the given path.\n- """\n+ """Create a new directory on the server within the given path."""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ name = safe_encode(name, "utf-8")\n \n code = 0\n- error = \'\'\n+ error = ""\n \n parentPath = self.normalizePath(path)\n parent = None\n@@ -273,20 +266,26 @@ def addFolder(self, path, name):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(\'filemanager_invalid_foldername\',\n- default=\'Invalid folder name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_foldername", default="Invalid folder name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(\'filemanager_error_folder_exists\',\n- default=\'Folder already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Folder already exists.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n@@ -294,400 +293,454 @@ def addFolder(self, path, name):\n except UnicodeDecodeError:\n error = translate(\n _(\n- \'filemanager_invalid_foldername\',\n- default=\'Invalid folder name.\'\n+ "filemanager_invalid_foldername",\n+ default="Invalid folder name.",\n ),\n- context=self.request\n+ context=self.request,\n )\n code = 1\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n- })\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n+ }\n+ )\n \n def addFile(self, path, name):\n- """Add a new empty file in the given directory\n- """\n+ """Add a new empty file in the given directory"""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ name = safe_encode(name, "utf-8")\n \n- error = \'\'\n+ error = ""\n code = 0\n \n parentPath = self.normalizePath(path)\n- newPath = f\'{parentPath}/{name}\'\n+ newPath = f"{parentPath}/{name}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(\'filemanager_invalid_filename\',\n- default=\'Invalid file name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_filename", default="Invalid file name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(\'filemanager_error_file_exists\',\n- default=\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n- self.resourceDirectory.writeFile(newPath, b\'\')\n-\n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n- \'path\': path\n- })\n+ self.resourceDirectory.writeFile(newPath, b"")\n+\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n+ "path": path,\n+ }\n+ )\n \n def delete(self, path):\n- """Delete the item at the given path.\n- """\n+ """Delete the item at the given path."""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n del parent[name]\n except KeyError:\n- error = translate(_(\'filemanager_error_file_not_found\',\n- default=\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'path\': self.normalizeReturnPath(path),\n- \'error\': error,\n- \'code\': code,\n- })\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "path": self.normalizeReturnPath(path),\n+ "error": error,\n+ "code": code,\n+ }\n+ )\n \n def renameFile(self, path, newName):\n- """Rename the item at the given path to the new name\n- """\n+ """Rename the item at the given path to the new name"""\n \n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- newName = safe_encode(newName, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ newName = safe_encode(newName, "utf-8")\n \n npath = self.normalizePath(path)\n- oldPath = newPath = \'/\'.join(npath.split(\'/\')[:-1])\n- oldName = npath.split(\'/\')[-1]\n+ oldPath = newPath = "/".join(npath.split("/")[:-1])\n+ oldName = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(oldPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if newName != oldName:\n if newName in parent:\n error = translate(\n _(\n- \'filemanager_error_file_exists\',\n- default=\'File already exists.\'\n+ "filemanager_error_file_exists",\n+ default="File already exists.",\n ),\n- context=self.request)\n+ context=self.request,\n+ )\n code = 1\n else:\n parent.rename(oldName, newName)\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'oldParent\': self.normalizeReturnPath(oldPath),\n- \'oldName\': oldName,\n- \'newParent\': self.normalizeReturnPath(newPath),\n- \'newName\': newName,\n- \'error\': error,\n- \'code\': code,\n- })\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "oldParent": self.normalizeReturnPath(oldPath),\n+ "oldName": oldName,\n+ "newParent": self.normalizeReturnPath(newPath),\n+ "newName": newName,\n+ "error": error,\n+ "code": code,\n+ }\n+ )\n \n def move(self, path, directory):\n- """Move the item at the given path to a new directory\n- """\n+ """Move the item at the given path to a new directory"""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- directory = safe_encode(directory, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ directory = safe_encode(directory, "utf-8")\n \n npath = self.normalizePath(path)\n newParentPath = self.normalizePath(directory)\n \n parentPath = self.parentPath(npath)\n- filename = npath.split(\'/\')[-1]\n+ filename = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n- newCanonicalPath = f\'{newParentPath}/{filename}\'\n+ error = ""\n+ newCanonicalPath = f"{newParentPath}/{filename}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n- return json.dumps({\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n- })\n+ return json.dumps(\n+ {\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n+ }\n+ )\n \n try:\n target = self.getObject(newParentPath)\n except KeyError:\n- error = translate(_(\'filemanager_error_folder_exists\',\n- default=\'Destination folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Destination folder not found.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n- return json.dumps({\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n- })\n+ return json.dumps(\n+ {\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n+ }\n+ )\n \n if filename not in parent:\n- error = translate(_(\'filemanager_error_file_not_found\',\n- default=\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n elif filename in target:\n- error = translate(_(\'filemanager_error_file_exists\',\n- default=\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n obj = parent[filename]\n del parent[filename]\n target[filename] = obj\n \n- return json.dumps({\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n- })\n+ return json.dumps(\n+ {\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n+ }\n+ )\n \n def do_action(self, action):\n- if action == \'dataTree\':\n+ if action == "dataTree":\n \n- def getDirectory(folder, relpath=\'\'):\n+ def getDirectory(folder, relpath=""):\n items = []\n for name in folder.listDirectory():\n obj = folder[name]\n- path = relpath + \'/\' + name\n+ path = relpath + "/" + name\n if IResourceDirectory.providedBy(obj):\n try:\n children = getDirectory(obj, path)\n except NotFound:\n children = []\n- items.append({\n- \'label\': name,\n- \'folder\': True,\n- \'path\': path,\n- \'children\': children\n- })\n+ items.append(\n+ {\n+ "label": name,\n+ "folder": True,\n+ "path": path,\n+ "children": children,\n+ }\n+ )\n else:\n items.append(self.getInfo(obj, path))\n return items\n \n return json.dumps(getDirectory(self.context))\n \n- if action == \'getFile\':\n- path = self.request.get(\'path\', \'\')\n+ if action == "getFile":\n+ path = self.request.get("path", "")\n return self.getFile(path)\n \n- if action == \'saveFile\':\n- path = self.request.get(\'path\', \'\')\n- data = self.request.get(\'data\', \'\')\n+ if action == "saveFile":\n+ path = self.request.get("path", "")\n+ data = self.request.get("data", "")\n return self.saveFile(path, data)\n \n- if action == \'addFolder\':\n- path = self.request.get(\'path\', \'\')\n- name = self.request.get(\'name\', \'\')\n+ if action == "addFolder":\n+ path = self.request.get("path", "")\n+ name = self.request.get("name", "")\n return self.addFolder(path, name)\n \n- if action == \'addFile\':\n- path = self.request.get(\'path\', \'\')\n- name = self.request.get(\'filename\', \'\')\n+ if action == "addFile":\n+ path = self.request.get("path", "")\n+ name = self.request.get("filename", "")\n return self.addFile(path, name)\n \n- if action == \'renameFile\':\n- path = self.request.get(\'path\', \'\')\n- name = self.request.get(\'filename\', \'\')\n+ if action == "renameFile":\n+ path = self.request.get("path", "")\n+ name = self.request.get("filename", "")\n return self.renameFile(path, name)\n \n- if action == \'delete\':\n- path = self.request.get(\'path\', \'\')\n+ if action == "delete":\n+ path = self.request.get("path", "")\n return self.delete(path)\n \n- if action == \'move\':\n- src_path = self.request.get(\'source\', \'\')\n- des_path = self.request.get(\'destination\', \'\')\n+ if action == "move":\n+ src_path = self.request.get("source", "")\n+ des_path = self.request.get("destination", "")\n return self.move(src_path, des_path)\n \n def download(self, path):\n- """Serve the requested file to the user\n- """\n+ """Serve the requested file to the user"""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n \n parent = self.getObject(parentPath)\n \n- self.request.response.setHeader(\'Content-Type\',\n- \'application/octet-stream\')\n+ self.request.response.setHeader("Content-Type", "application/octet-stream")\n self.request.response.setHeader(\n- \'Content-Disposition\',\n- f\'attachment; filename="{name}"\'\n+ "Content-Disposition", f\'attachment; filename="{name}"\'\n )\n \n # TODO: Use streams here if we can\n return parent.readFile(name)\n \n def __call__(self):\n- action = self.request.get(\'action\')\n+ action = self.request.get("action")\n return self.do_action(action)\n \n \n class FileManager(BrowserView):\n- """Render the file manager and support its AJAX requests.\n- """\n-\n- previewTemplate = ViewPageTemplateFile(\'preview.pt\')\n- staticFiles = \'++resource++plone.resourceeditor/filemanager\'\n- imageExtensions = [\'png\', \'gif\', \'jpg\', \'jpeg\']\n- knownExtensions = [\'css\', \'html\', \'htm\', \'txt\', \'xml\', \'js\', \'cfg\']\n- capabilities = [\'download\', \'rename\', \'delete\']\n-\n- extensionsWithIcons = frozenset([\n- \'aac\', \'avi\', \'bmp\', \'chm\', \'css\', \'dll\', \'doc\', \'fla\',\n- \'gif\', \'htm\', \'html\', \'ini\', \'jar\', \'jpeg\', \'jpg\', \'js\',\n- \'lasso\', \'mdb\', \'mov\', \'mp3\', \'mpg\', \'pdf\', \'php\', \'png\',\n- \'ppt\', \'py\', \'rb\', \'real\', \'reg\', \'rtf\', \'sql\', \'swf\', \'txt\',\n- \'vbs\', \'wav\', \'wma\', \'wmv\', \'xls\', \'xml\', \'xsl\', \'zip\',\n- ])\n-\n- protectedActions = (\n- \'addfolder\', \'add\', \'addnew\',\n- \'rename\', \'delete\'\n+ """Render the file manager and support its AJAX requests."""\n+\n+ previewTemplate = ViewPageTemplateFile("preview.pt")\n+ staticFiles = "++resource++plone.resourceeditor/filemanager"\n+ imageExtensions = ["png", "gif", "jpg", "jpeg"]\n+ knownExtensions = ["css", "html", "htm", "txt", "xml", "js", "cfg"]\n+ capabilities = ["download", "rename", "delete"]\n+\n+ extensionsWithIcons = frozenset(\n+ [\n+ "aac",\n+ "avi",\n+ "bmp",\n+ "chm",\n+ "css",\n+ "dll",\n+ "doc",\n+ "fla",\n+ "gif",\n+ "htm",\n+ "html",\n+ "ini",\n+ "jar",\n+ "jpeg",\n+ "jpg",\n+ "js",\n+ "lasso",\n+ "mdb",\n+ "mov",\n+ "mp3",\n+ "mpg",\n+ "pdf",\n+ "php",\n+ "png",\n+ "ppt",\n+ "py",\n+ "rb",\n+ "real",\n+ "reg",\n+ "rtf",\n+ "sql",\n+ "swf",\n+ "txt",\n+ "vbs",\n+ "wav",\n+ "wma",\n+ "wmv",\n+ "xls",\n+ "xml",\n+ "xsl",\n+ "zip",\n+ ]\n )\n \n+ protectedActions = ("addfolder", "add", "addnew", "rename", "delete")\n+\n def pattern_options(self):\n site = getSite()\n- viewName = \'@@plone.resourceeditor.filemanager-actions\'\n- return json.dumps({\n- \'actionUrl\': \'{}/++{}++{}/{}\'.format(\n- site.absolute_url(),\n- self.context.__parent__.__parent__.__name__,\n- self.context.__name__,\n- viewName\n- )\n- })\n+ viewName = "@@plone.resourceeditor.filemanager-actions"\n+ return json.dumps(\n+ {\n+ "actionUrl": "{}/++{}++{}/{}".format(\n+ site.absolute_url(),\n+ self.context.__parent__.__parent__.__name__,\n+ self.context.__name__,\n+ viewName,\n+ )\n+ }\n+ )\n \n def mode_selector(self, form):\n # AJAX methods called by the file manager\n- mode = form[\'mode\']\n+ mode = form["mode"]\n \n if mode in self.protectedActions:\n authorize(self.context, self.request)\n \n- response = {\'error:\': \'Unknown request\', \'code\': -1}\n+ response = {"error:": "Unknown request", "code": -1}\n textareaWrap = False\n \n- if mode == \'getfolder\':\n+ if mode == "getfolder":\n response = self.getFolder(\n- path=urllib.parse.unquote(form[\'path\']),\n- getSizes=form.get(\'getsizes\', \'false\') == \'true\'\n+ path=urllib.parse.unquote(form["path"]),\n+ getSizes=form.get("getsizes", "false") == "true",\n )\n- elif mode == \'getinfo\':\n+ elif mode == "getinfo":\n response = self.getInfo(\n- path=urllib.parse.unquote(form[\'path\']),\n- getSize=form.get(\'getsize\', \'false\') == \'true\'\n+ path=urllib.parse.unquote(form["path"]),\n+ getSize=form.get("getsize", "false") == "true",\n )\n- elif mode == \'addfolder\':\n+ elif mode == "addfolder":\n response = self.addFolder(\n- path=urllib.parse.unquote(form[\'path\']),\n- name=urllib.parse.unquote(form[\'name\'])\n+ path=urllib.parse.unquote(form["path"]),\n+ name=urllib.parse.unquote(form["name"]),\n )\n- elif mode == \'add\':\n+ elif mode == "add":\n textareaWrap = True\n response = self.add(\n- path=urllib.parse.unquote(form[\'currentpath\']),\n- newfile=form[\'newfile\'],\n- replacepath=form.get(\'replacepath\', None)\n+ path=urllib.parse.unquote(form["currentpath"]),\n+ newfile=form["newfile"],\n+ replacepath=form.get("replacepath", None),\n )\n- elif mode == \'addnew\':\n+ elif mode == "addnew":\n response = self.addNew(\n- path=urllib.parse.unquote(form[\'path\']),\n- name=urllib.parse.unquote(form[\'name\'])\n+ path=urllib.parse.unquote(form["path"]),\n+ name=urllib.parse.unquote(form["name"]),\n )\n- elif mode == \'rename\':\n+ elif mode == "rename":\n response = self.rename(\n- path=urllib.parse.unquote(form[\'old\']),\n- newName=urllib.parse.unquote(form[\'new\'])\n- )\n- elif mode == \'delete\':\n- response = self.delete(\n- path=urllib.parse.unquote(form[\'path\'])\n+ path=urllib.parse.unquote(form["old"]),\n+ newName=urllib.parse.unquote(form["new"]),\n )\n- elif mode == \'move\':\n+ elif mode == "delete":\n+ response = self.delete(path=urllib.parse.unquote(form["path"]))\n+ elif mode == "move":\n response = self.move(\n- path=urllib.parse.unquote(form[\'path\']),\n- directory=urllib.parse.unquote(form[\'directory\'])\n- )\n- elif mode == \'download\':\n- return self.download(\n- path=urllib.parse.unquote(form[\'path\'])\n+ path=urllib.parse.unquote(form["path"]),\n+ directory=urllib.parse.unquote(form["directory"]),\n )\n+ elif mode == "download":\n+ return self.download(path=urllib.parse.unquote(form["path"]))\n if textareaWrap:\n- self.request.response.setHeader(\'Content-Type\', \'text/html\')\n- return f\'\'\n- self.request.response.setHeader(\'Content-Type\',\n- \'application/json\')\n+ self.request.response.setHeader("Content-Type", "text/html")\n+ return f""\n+ self.request.response.setHeader("Content-Type", "application/json")\n return json.dumps(response)\n \n def __call__(self):\n # make sure theme is disable for these requests\n- self.request.response.setHeader(\'X-Theme-Disabled\', \'True\')\n- self.request[\'HTTP_X_THEME_ENABLED\'] = False\n+ self.request.response.setHeader("X-Theme-Disabled", "True")\n+ self.request["HTTP_X_THEME_ENABLED"] = False\n \n self.setup()\n form = self.request.form\n \n # AJAX methods called by the file manager\n- if \'mode\' in form:\n+ if "mode" in form:\n return self.mode_selector(form)\n \n # Rendering the view\n@@ -699,7 +752,7 @@ def setup(self):\n \n @zproperty.Lazy\n def portalUrl(self):\n- return getToolByName(self.context, \'portal_url\')()\n+ return getToolByName(self.context, "portal_url")()\n \n @zproperty.Lazy\n def resourceDirectory(self):\n@@ -711,15 +764,13 @@ def resourceType(self):\n \n @zproperty.Lazy\n def baseUrl(self):\n- return \'{}/++{}++{}\'.format(\n- self.portalUrl,\n- self.resourceType,\n- self.resourceDirectory.__name__\n+ return "{}/++{}++{}".format(\n+ self.portalUrl, self.resourceType, self.resourceDirectory.__name__\n )\n \n @zproperty.Lazy\n def fileConnector(self):\n- return f\'{self.baseUrl}/@@{self.__name__}\'\n+ return f"{self.baseUrl}/@@{self.__name__}"\n \n @zproperty.Lazy\n def filemanagerConfiguration(self):\n@@ -737,21 +788,21 @@ def filemanagerConfiguration(self):\n )\n \n def normalizePath(self, path):\n- if path.startswith(\'/\'):\n+ if path.startswith("/"):\n path = path[1:]\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n return path\n \n def normalizeReturnPath(self, path):\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n- if not path.startswith(\'/\'):\n- path = \'/\' + path\n+ if not path.startswith("/"):\n+ path = "/" + path\n return path\n \n def parentPath(self, path):\n- return \'/\'.join(path.split(\'/\')[:-1])\n+ return "/".join(path.split("/")[:-1])\n \n # AJAX responses\n \n@@ -771,7 +822,7 @@ def getFolder(self, path, getSizes=False):\n in a file system tree.\n """\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n folders = []\n files = []\n@@ -781,11 +832,9 @@ def getFolder(self, path, getSizes=False):\n \n for name in folder.listDirectory():\n if IResourceDirectory.providedBy(folder[name]):\n- folders.append(self.getInfo(\n- path=f\'{path}/{name}/\', getSize=getSizes))\n+ folders.append(self.getInfo(path=f"{path}/{name}/", getSize=getSizes))\n else:\n- files.append(self.getInfo(\n- path=f\'{path}/{name}\', getSize=getSizes))\n+ files.append(self.getInfo(path=f"{path}/{name}", getSize=getSizes))\n return folders + files\n \n def getInfo(self, path, getSize=False):\n@@ -796,89 +845,81 @@ def getInfo(self, path, getSize=False):\n returned.\n """\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n path = self.normalizePath(path)\n obj = self.getObject(path)\n \n filename = obj.__name__\n- error = \'\'\n+ error = ""\n errorCode = 0\n \n properties = {\n- \'dateModified\': None,\n+ "dateModified": None,\n }\n \n if isinstance(obj, File):\n- properties[\'dateModified\'] = DateTime(obj._p_mtime).strftime(\'%c\')\n+ properties["dateModified"] = DateTime(obj._p_mtime).strftime("%c")\n size = obj.get_size() / 1024\n if size < 1024:\n- size_specifier = \'kb\'\n+ size_specifier = "kb"\n else:\n- size_specifier = \'mb\'\n+ size_specifier = "mb"\n size = size / 1024\n- properties[\'size\'] = \'{}{}\'.format(\n+ properties["size"] = "{}{}".format(\n size,\n- translate(_(f\'filemanager_{size_specifier}\',\n- default=size_specifier), context=self.request)\n+ translate(\n+ _(f"filemanager_{size_specifier}", default=size_specifier),\n+ context=self.request,\n+ ),\n )\n \n- fileType = \'txt\'\n+ fileType = "txt"\n \n siteUrl = self.portalUrl\n resourceName = self.resourceDirectory.__name__\n \n- preview = \'{}/{}/images/fileicons/default.png\'.format(\n- siteUrl,\n- self.staticFiles\n- )\n+ preview = f"{siteUrl}/{self.staticFiles}/images/fileicons/default.png"\n \n if IResourceDirectory.providedBy(obj):\n- preview = \'{}/{}/images/fileicons/_Open.png\'.format(\n- siteUrl,\n- self.staticFiles\n+ preview = "{}/{}/images/fileicons/_Open.png".format(\n+ siteUrl, self.staticFiles\n )\n- fileType = \'dir\'\n- path = path + \'/\'\n+ fileType = "dir"\n+ path = path + "/"\n else:\n fileType = self.getExtension(path, obj)\n if fileType in self.imageExtensions:\n- preview = \'{}/++{}++{}/{}\'.format(\n- siteUrl,\n- self.resourceType,\n- resourceName,\n- path\n+ preview = "{}/++{}++{}/{}".format(\n+ siteUrl, self.resourceType, resourceName, path\n )\n elif fileType in self.extensionsWithIcons:\n- preview = \'{}/{}/images/fileicons/{}.png\'.format(\n- siteUrl,\n- self.staticFiles,\n- fileType\n+ preview = "{}/{}/images/fileicons/{}.png".format(\n+ siteUrl, self.staticFiles, fileType\n )\n \n if isinstance(obj, Image):\n- properties[\'height\'] = obj.height\n- properties[\'width\'] = obj.width\n+ properties["height"] = obj.height\n+ properties["width"] = obj.width\n \n return {\n- \'path\': self.normalizeReturnPath(path),\n- \'filename\': filename,\n- \'fileType\': fileType,\n- \'preview\': preview,\n- \'properties\': properties,\n- \'error\': error,\n- \'code\': errorCode,\n+ "path": self.normalizeReturnPath(path),\n+ "filename": filename,\n+ "fileType": fileType,\n+ "preview": preview,\n+ "properties": properties,\n+ "error": error,\n+ "code": errorCode,\n }\n \n def addFolder(self, path, name):\n- """Create a new directory on the server within the given path.\n- """\n+ """Create a new directory on the server within the given path."""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ name = safe_encode(name, "utf-8")\n \n code = 0\n- error = \'\'\n+ error = ""\n \n parentPath = self.normalizePath(path)\n parent = None\n@@ -886,20 +927,26 @@ def addFolder(self, path, name):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(\'filemanager_invalid_foldername\',\n- default=\'Invalid folder name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_foldername", default="Invalid folder name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(\'filemanager_error_folder_exists\',\n- default=\'Folder already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Folder already exists.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n@@ -907,18 +954,18 @@ def addFolder(self, path, name):\n except UnicodeDecodeError:\n error = translate(\n _(\n- \'filemanager_invalid_foldername\',\n- default=\'Invalid folder name.\'\n+ "filemanager_invalid_foldername",\n+ default="Invalid folder name.",\n ),\n- context=self.request\n+ context=self.request,\n )\n code = 1\n \n return {\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n }\n \n def add(self, path, newfile, replacepath=None):\n@@ -933,223 +980,241 @@ def add(self, path, newfile, replacepath=None):\n """\n \n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n if six.PY2 and replacepath is not None:\n- replacepath = safe_encode(replacepath, \'utf-8\')\n+ replacepath = safe_encode(replacepath, "utf-8")\n \n parentPath = self.normalizePath(path)\n \n- error = \'\'\n+ error = ""\n code = 0\n \n name = newfile.filename\n if six.PY2 and isinstance(name, str):\n- name = safe_encode(name, \'utf-8\')\n+ name = safe_encode(name, "utf-8")\n \n if replacepath:\n newPath = replacepath\n- parentPath = \'/\'.join(replacepath.split(\'/\')[:-1])\n+ parentPath = "/".join(replacepath.split("/")[:-1])\n else:\n- newPath = f\'{parentPath}/{name}\'\n+ newPath = f"{parentPath}/{name}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if name in parent and not replacepath:\n- error = translate(_(\'filemanager_error_file_exists\',\n- default=\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n self.resourceDirectory.writeFile(newPath, newfile)\n except ValueError:\n- error = translate(_(\'filemanager_error_file_invalid\',\n- default=\'Could not read file.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_file_invalid",\n+ default="Could not read file.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n \n return {\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'path\': self.normalizeReturnPath(path),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "path": self.normalizeReturnPath(path),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n }\n \n def addNew(self, path, name):\n- """Add a new empty file in the given directory\n- """\n+ """Add a new empty file in the given directory"""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ name = safe_encode(name, "utf-8")\n \n- error = \'\'\n+ error = ""\n code = 0\n \n parentPath = self.normalizePath(path)\n- newPath = f\'{parentPath}/{name}\'\n+ newPath = f"{parentPath}/{name}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(\'filemanager_invalid_filename\',\n- default=\'Invalid file name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_filename", default="Invalid file name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(\'filemanager_error_file_exists\',\n- default=\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n- self.resourceDirectory.writeFile(newPath, b\'\')\n+ self.resourceDirectory.writeFile(newPath, b"")\n \n return {\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n }\n \n def rename(self, path, newName):\n- """Rename the item at the given path to the new name\n- """\n+ """Rename the item at the given path to the new name"""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- newName = safe_encode(newName, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ newName = safe_encode(newName, "utf-8")\n \n npath = self.normalizePath(path)\n- oldPath = newPath = \'/\'.join(npath.split(\'/\')[:-1])\n- oldName = npath.split(\'/\')[-1]\n+ oldPath = newPath = "/".join(npath.split("/")[:-1])\n+ oldName = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(oldPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if newName != oldName:\n if newName in parent:\n error = translate(\n- _(\'filemanager_error_file_exists\',\n- default=\'File already exists.\'),\n- context=self.request)\n+ _(\n+ "filemanager_error_file_exists",\n+ default="File already exists.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n else:\n parent.rename(oldName, newName)\n \n return {\n- \'oldParent\': self.normalizeReturnPath(oldPath),\n- \'oldName\': oldName,\n- \'newParent\': self.normalizeReturnPath(newPath),\n- \'newName\': newName,\n- \'error\': error,\n- \'code\': code,\n+ "oldParent": self.normalizeReturnPath(oldPath),\n+ "oldName": oldName,\n+ "newParent": self.normalizeReturnPath(newPath),\n+ "newName": newName,\n+ "error": error,\n+ "code": code,\n }\n \n def delete(self, path):\n- """Delete the item at the given path.\n- """\n+ """Delete the item at the given path."""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n del parent[name]\n except KeyError:\n- error = translate(_(\'filemanager_error_file_not_found\',\n- default=\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n \n return {\n- \'path\': self.normalizeReturnPath(path),\n- \'error\': error,\n- \'code\': code,\n+ "path": self.normalizeReturnPath(path),\n+ "error": error,\n+ "code": code,\n }\n \n def move(self, path, directory):\n- """Move the item at the given path to a new directory\n- """\n+ """Move the item at the given path to a new directory"""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- directory = safe_encode(directory, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n+ directory = safe_encode(directory, "utf-8")\n \n npath = self.normalizePath(path)\n newParentPath = self.normalizePath(directory)\n \n parentPath = self.parentPath(npath)\n- filename = npath.split(\'/\')[-1]\n+ filename = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n- newCanonicalPath = f\'{newParentPath}/{filename}\'\n+ error = ""\n+ newCanonicalPath = f"{newParentPath}/{filename}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(\'filemanager_invalid_parent\',\n- default=\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n return {\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n }\n \n try:\n target = self.getObject(newParentPath)\n except KeyError:\n- error = translate(_(\'filemanager_error_folder_exists\',\n- default=\'Destination folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Destination folder not found.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n return {\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n }\n \n if filename not in parent:\n- error = translate(_(\'filemanager_error_file_not_found\',\n- default=\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n elif filename in target:\n- error = translate(_(\'filemanager_error_file_exists\',\n- default=\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n obj = parent[filename]\n@@ -1157,28 +1222,25 @@ def move(self, path, directory):\n target[filename] = obj\n \n return {\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n }\n \n def download(self, path):\n- """Serve the requested file to the user\n- """\n+ """Serve the requested file to the user"""\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n \n parent = self.getObject(parentPath)\n \n- self.request.response.setHeader(\'Content-Type\',\n- \'application/octet-stream\')\n+ self.request.response.setHeader("Content-Type", "application/octet-stream")\n self.request.response.setHeader(\n- \'Content-Disposition\',\n- f\'attachment; filename="{name}"\'\n+ "Content-Disposition", f\'attachment; filename="{name}"\'\n )\n \n # TODO: Use streams here if we can\n@@ -1191,7 +1253,10 @@ def getObject(self, path):\n return self.resourceDirectory\n try:\n return self.resourceDirectory[path]\n- except (KeyError, NotFound,):\n+ except (\n+ KeyError,\n+ NotFound,\n+ ):\n raise KeyError(path)\n \n def getExtension(self, path, obj):\n@@ -1201,8 +1266,8 @@ def getExtension(self, path, obj):\n ct = obj.getContentType()\n if ct:\n # take content type of the file over extension if available\n- if \'/\' in ct:\n- _ext = ct.split(\'/\')[1].lower()\n+ if "/" in ct:\n+ _ext = ct.split("/")[1].lower()\n if _ext in self.extensionsWithIcons:\n return _ext\n return ext\n@@ -1211,56 +1276,56 @@ def getExtension(self, path, obj):\n def getFile(self, path):\n self.setup()\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n+ path = safe_encode(path, "utf-8")\n \n path = self.normalizePath(path)\n file = self.context.context.unrestrictedTraverse(path)\n ext = self.getExtension(path, file)\n- result = {\'ext\': ext}\n+ result = {"ext": ext}\n if ext not in self.imageExtensions:\n- result[\'contents\'] = str(file.data)\n+ result["contents"] = str(file.data)\n else:\n info = self.getInfo(path)\n- result[\'info\'] = self.previewTemplate(info=info)\n+ result["info"] = self.previewTemplate(info=info)\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n+ self.request.response.setHeader("Content-Type", "application/json")\n return json.dumps(result)\n \n def saveFile(self, path, value):\n- path = self.request.form.get(\'path\', path)\n- value = self.request.form.get(\'value\', value)\n+ path = self.request.form.get("path", path)\n+ value = self.request.form.get("value", value)\n if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- value = safe_encode(value, \'utf-8\')\n- path = path.lstrip(\'/\')\n- value = value.replace(\'\\r\\n\', \'\\n\')\n+ path = safe_encode(path, "utf-8")\n+ value = safe_encode(value, "utf-8")\n+ path = path.lstrip("/")\n+ value = value.replace("\\r\\n", "\\n")\n self.context.writeFile(path, value)\n- return \' \' # Zope no likey empty responses\n+ return " " # Zope no likey empty responses\n \n def filetree(self):\n+ foldersOnly = bool(self.request.get("foldersOnly", False))\n \n- foldersOnly = bool(self.request.get(\'foldersOnly\', False))\n-\n- def getFolder(root, relpath=\'\'):\n+ def getFolder(root, relpath=""):\n result = []\n for name in root.listDirectory():\n- path = f\'{relpath}/{name}\'\n+ path = f"{relpath}/{name}"\n if IResourceDirectory.providedBy(root[name]):\n- item = {\n- \'title\': name,\n- \'key\': path,\n- \'isFolder\': True\n- }\n- item[\'children\'] = getFolder(root[name], path)\n+ item = {"title": name, "key": path, "isFolder": True}\n+ item["children"] = getFolder(root[name], path)\n result.append(item)\n elif not foldersOnly:\n- item = {\'title\': name, \'key\': path}\n+ item = {"title": name, "key": path}\n result.append(item)\n return result\n- return json.dumps([{\n- \'title\': \'/\',\n- \'key\': \'/\',\n- \'isFolder\': True,\n- \'expand\': True,\n- \'children\': getFolder(self.context)\n- }])\n+\n+ return json.dumps(\n+ [\n+ {\n+ "title": "/",\n+ "key": "/",\n+ "isFolder": True,\n+ "expand": True,\n+ "children": getFolder(self.context),\n+ }\n+ ]\n+ )\ndiff --git a/plone/resourceeditor/testing.py b/plone/resourceeditor/testing.py\nindex 10ada31..e4ef105 100644\n--- a/plone/resourceeditor/testing.py\n+++ b/plone/resourceeditor/testing.py\n@@ -10,19 +10,18 @@ class PloneResourceEditor(PloneSandboxLayer):\n def setUpZope(self, app, configurationContext):\n # Load ZCML\n import plone.resourceeditor\n+\n self.loadZCML(\n- \'configure.zcml\',\n- package=plone.resourceeditor,\n- context=configurationContext\n+ "configure.zcml", package=plone.resourceeditor, context=configurationContext\n )\n \n def setUpPloneSite(self, portal):\n # install plone.resource\n- applyProfile(portal, \'plone.resource:default\')\n+ applyProfile(portal, "plone.resource:default")\n \n \n PLONE_RESOURCE_EDITOR_FIXTURE = PloneResourceEditor()\n PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING = IntegrationTesting(\n- bases=(PLONE_RESOURCE_EDITOR_FIXTURE, ),\n- name=\'plone.resourceeditor:Integration\',\n+ bases=(PLONE_RESOURCE_EDITOR_FIXTURE,),\n+ name="plone.resourceeditor:Integration",\n )\ndiff --git a/plone/resourceeditor/tests/test_file_manager.py b/plone/resourceeditor/tests/test_file_manager.py\nindex d0049ec..cc9b460 100644\n--- a/plone/resourceeditor/tests/test_file_manager.py\n+++ b/plone/resourceeditor/tests/test_file_manager.py\n@@ -5,14 +5,13 @@\n \n \n class TestResourceEditorOperations(unittest.TestCase):\n-\n layer = PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n \n- def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n+ def _make_directory(self, resourcetype="theme", resourcename="mytheme"):\n from plone.resource.interfaces import IResourceDirectory\n from zope.component import getUtility\n \n- resources = getUtility(IResourceDirectory, name=\'persistent\')\n+ resources = getUtility(IResourceDirectory, name="persistent")\n resources.makeDirectory(resourcetype)\n resources[resourcetype].makeDirectory(resourcename)\n \n@@ -20,375 +19,398 @@ def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n \n def test_getinfo(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- r.writeFile(\'test.txt\', b\'A text file\')\n+ r.writeFile("test.txt", b"A text file")\n \n- view = FileManager(r, self.layer[\'request\'])\n- info = view.getInfo(\'/test.txt\')\n+ view = FileManager(r, self.layer["request"])\n+ info = view.getInfo("/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'fileType\'], \'txt\')\n- self.assertEqual(info[\'filename\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["fileType"], "txt")\n+ self.assertEqual(info["filename"], "test.txt")\n+ self.assertEqual(info["path"], "/test.txt")\n \n def test_getfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'beta.txt\', b\'Beta\')\n- r[\'alpha\'].makeDirectory(\'delta\')\n- r[\'alpha\'][\'delta\'].writeFile(\'gamma.css\', b\'body\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("beta.txt", b"Beta")\n+ r["alpha"].makeDirectory("delta")\n+ r["alpha"]["delta"].writeFile("gamma.css", b"body")\n \n- view = FileManager(r, self.layer[\'request\'])\n- info = view.getFolder(\'/alpha\')\n+ view = FileManager(r, self.layer["request"])\n+ info = view.getFolder("/alpha")\n \n self.assertEqual(len(info), 2)\n \n- self.assertEqual(info[0][\'code\'], 0)\n- self.assertEqual(info[0][\'error\'], \'\')\n- self.assertEqual(info[0][\'fileType\'], \'dir\')\n- self.assertEqual(info[0][\'filename\'], \'delta\')\n- self.assertEqual(info[0][\'path\'], \'/alpha/delta\')\n+ self.assertEqual(info[0]["code"], 0)\n+ self.assertEqual(info[0]["error"], "")\n+ self.assertEqual(info[0]["fileType"], "dir")\n+ self.assertEqual(info[0]["filename"], "delta")\n+ self.assertEqual(info[0]["path"], "/alpha/delta")\n \n- self.assertEqual(info[1][\'code\'], 0)\n- self.assertEqual(info[1][\'error\'], \'\')\n- self.assertEqual(info[1][\'fileType\'], \'txt\')\n- self.assertEqual(info[1][\'filename\'], \'beta.txt\')\n- self.assertEqual(info[1][\'path\'], \'/alpha/beta.txt\')\n+ self.assertEqual(info[1]["code"], 0)\n+ self.assertEqual(info[1]["error"], "")\n+ self.assertEqual(info[1]["fileType"], "txt")\n+ self.assertEqual(info[1]["filename"], "beta.txt")\n+ self.assertEqual(info[1]["path"], "/alpha/beta.txt")\n \n def test_addfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addFolder(\'/\', \'alpha\')\n+ info = view.addFolder("/", "alpha")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n- info = view.addFolder(\'/alpha\', \'beta\')\n+ info = view.addFolder("/alpha", "beta")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_addfolder_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addFolder(\'/\', \'alpha\')\n+ info = view.addFolder("/", "alpha")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n def test_addfolder_invalid_name(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addFolder(\'/\', \'foo\' + char)\n+ info = view.addFolder("/", "foo" + char)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'foo\' + char)\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "foo" + char)\n \n def test_addfolder_invalid_parent(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addFolder(\'/alpha\', \'beta\')\n+ info = view.addFolder("/alpha", "beta")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_add(self):\n from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n- info = view.add(\'/\', d)\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n+ info = view.add("/", d)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n+ self.assertEqual(info["parent"], "/")\n \n def test_add_subfolder(self):\n from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n \n- info = view.add(\'/alpha\', d)\n+ info = view.add("/alpha", d)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/alpha\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/alpha")\n+ self.assertEqual(info["parent"], "/alpha")\n \n def test_add_exists(self):\n from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'boo\')\n+ r.writeFile("test.txt", b"boo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n \n- info = view.add(\'/\', d)\n+ info = view.add("/", d)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'boo\')\n+ self.assertEqual(r.readFile("test.txt"), b"boo")\n \n def test_add_replace(self):\n from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'boo\')\n+ r.writeFile("test.txt", b"boo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n \n- info = view.add(\'/\', d, \'/test.txt\')\n+ info = view.add("/", d, "/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n+ self.assertEqual(info["parent"], "/")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("test.txt"), b"foo")\n \n def test_addnew(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addNew(\'/\', \'test.txt\')\n+ info = view.addNew("/", "test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["parent"], "/")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'\')\n+ self.assertEqual(r.readFile("test.txt"), b"")\n \n def test_addnew_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addNew(\'/\', \'test.txt\')\n+ info = view.addNew("/", "test.txt")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("test.txt"), b"foo")\n \n def test_addnew_invalidname(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addNew(\'/\', \'foo\' + char)\n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ info = view.addNew("/", "foo" + char)\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n def test_rename(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.rename(\'/test.txt\', \'foo.txt\')\n+ info = view.rename("/test.txt", "foo.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("foo.txt"), b"foo")\n \n def test_rename_subfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.rename(\'/alpha/test.txt\', \'foo.txt\')\n+ info = view.rename("/alpha/test.txt", "foo.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/alpha\')\n- self.assertEqual(info[\'newParent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/alpha")\n+ self.assertEqual(info["newParent"], "/alpha")\n \n- self.assertEqual(r[\'alpha\'].readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r["alpha"].readFile("foo.txt"), b"foo")\n \n def test_rename_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n- r.writeFile(\'foo.txt\', b\'bar\')\n+ r.writeFile("test.txt", b"foo")\n+ r.writeFile("foo.txt", b"bar")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.rename(\'/test.txt\', \'foo.txt\')\n+ info = view.rename("/test.txt", "foo.txt")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'bar\')\n+ self.assertEqual(r.readFile("foo.txt"), b"bar")\n \n def test_delete(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n+ self.assertFalse("test.txt" in r)\n \n def test_delete_subfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.delete(\'/alpha/test.txt\')\n+ info = view.delete("/alpha/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r[\'alpha\'])\n+ self.assertFalse("test.txt" in r["alpha"])\n \n def test_delete_notfound(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n def test_move(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n- self.assertEqual(b\'foo\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertFalse("test.txt" in r)\n+ self.assertEqual(b"foo", r["alpha"].readFile("test.txt"))\n \n def test_move_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'bar\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"bar")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n- self.assertEqual(b\'bar\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertTrue("test.txt" in r)\n+ self.assertEqual(b"bar", r["alpha"].readFile("test.txt"))\n \n def test_move_invalid_parent(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n+ self.assertTrue("test.txt" in r)\n \n def test_download(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n- self.assertEqual(b\'foo\', view.download(\'/test.txt\'))\n+ view = FileManager(r, self.layer["request"])\n+ self.assertEqual(b"foo", view.download("/test.txt"))\ndiff --git a/plone/resourceeditor/tests/test_file_manager_action.py b/plone/resourceeditor/tests/test_file_manager_action.py\nindex 3f999a2..46d1b94 100644\n--- a/plone/resourceeditor/tests/test_file_manager_action.py\n+++ b/plone/resourceeditor/tests/test_file_manager_action.py\n@@ -5,14 +5,13 @@\n \n \n class TestResourceEditorOperations(unittest.TestCase):\n-\n layer = PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n \n- def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n+ def _make_directory(self, resourcetype="theme", resourcename="mytheme"):\n from plone.resource.interfaces import IResourceDirectory\n from zope.component import getUtility\n \n- resources = getUtility(IResourceDirectory, name=\'persistent\')\n+ resources = getUtility(IResourceDirectory, name="persistent")\n resources.makeDirectory(resourcetype)\n resources[resourcetype].makeDirectory(resourcename)\n \n@@ -20,330 +19,350 @@ def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n \n def test_getinfo(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- r.writeFile(\'test.txt\', b\'A text file\')\n- view = FileManagerActions(r, self.layer[\'request\'])\n- info = view.getInfo(r[\'test.txt\'])\n+ r.writeFile("test.txt", b"A text file")\n+ view = FileManagerActions(r, self.layer["request"])\n+ info = view.getInfo(r["test.txt"])\n \n- self.assertEqual(info[\'fileType\'], \'txt\')\n- self.assertEqual(info[\'filename\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n+ self.assertEqual(info["fileType"], "txt")\n+ self.assertEqual(info["filename"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n \n def test_getfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'beta.txt\', b\'Beta\')\n- r[\'alpha\'].makeDirectory(\'delta\')\n- r[\'alpha\'][\'delta\'].writeFile(\'gamma.css\', b\'body\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("beta.txt", b"Beta")\n+ r["alpha"].makeDirectory("delta")\n+ r["alpha"]["delta"].writeFile("gamma.css", b"body")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n- info = view.getFolder(\'/alpha\')\n+ view = FileManagerActions(r, self.layer["request"])\n+ info = view.getFolder("/alpha")\n \n self.assertEqual(len(info), 2)\n- self.assertEqual(info[0][\'fileType\'], \'dir\')\n- self.assertEqual(info[0][\'filename\'], \'delta\')\n- self.assertEqual(info[0][\'path\'], \'/alpha/delta/\')\n+ self.assertEqual(info[0]["fileType"], "dir")\n+ self.assertEqual(info[0]["filename"], "delta")\n+ self.assertEqual(info[0]["path"], "/alpha/delta/")\n \n- self.assertEqual(info[1][\'fileType\'], \'txt\')\n- self.assertEqual(info[1][\'filename\'], \'beta.txt\')\n- self.assertEqual(info[1][\'path\'], \'/alpha/beta.txt\')\n+ self.assertEqual(info[1]["fileType"], "txt")\n+ self.assertEqual(info[1]["filename"], "beta.txt")\n+ self.assertEqual(info[1]["path"], "/alpha/beta.txt")\n \n def test_addfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info_str = view.addFolder(\'/\', \'alpha\')\n+ info_str = view.addFolder("/", "alpha")\n info = json.loads(info_str)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n- info_str = view.addFolder(\'/alpha\', \'beta\')\n+ info_str = view.addFolder("/alpha", "beta")\n info = json.loads(info_str)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_addfolder_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info_str = view.addFolder(\'/\', \'alpha\')\n+ info_str = view.addFolder("/", "alpha")\n info = json.loads(info_str)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n def test_addfolder_invalid_name(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addFolder(\'/\', \'foo\' + char)\n+ info = view.addFolder("/", "foo" + char)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'foo\' + char)\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "foo" + char)\n \n def test_addfolder_invalid_parent(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.addFolder(\'/alpha\', \'beta\')\n+ info = view.addFolder("/alpha", "beta")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_add(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- d = \'test.txt\'\n+ d = "test.txt"\n \n- info = view.addFile(\'/\', d)\n+ info = view.addFile("/", d)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n+ self.assertEqual(info["parent"], "/")\n \n def test_add_subfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- d = \'test.txt\'\n+ d = "test.txt"\n \n- info = view.addFile(\'/alpha\', d)\n+ info = view.addFile("/alpha", d)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/alpha\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/alpha")\n+ self.assertEqual(info["parent"], "/alpha")\n \n def test_add_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'boo\')\n+ r.writeFile("test.txt", b"boo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- d = \'test.txt\'\n+ d = "test.txt"\n \n- info = view.addFile(\'/\', d)\n+ info = view.addFile("/", d)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'boo\')\n+ self.assertEqual(r.readFile("test.txt"), b"boo")\n \n def test_addnew_invalidname(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addFile(\'/\', \'foo\' + char)\n+ info = view.addFile("/", "foo" + char)\n info = json.loads(info)\n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n def test_rename(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.renameFile(\'/test.txt\', \'foo.txt\')\n+ info = view.renameFile("/test.txt", "foo.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("foo.txt"), b"foo")\n \n def test_rename_subfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.renameFile(\'/alpha/test.txt\', \'foo.txt\')\n+ info = view.renameFile("/alpha/test.txt", "foo.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/alpha\')\n- self.assertEqual(info[\'newParent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/alpha")\n+ self.assertEqual(info["newParent"], "/alpha")\n \n- self.assertEqual(r[\'alpha\'].readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r["alpha"].readFile("foo.txt"), b"foo")\n \n def test_rename_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n- r.writeFile(\'foo.txt\', b\'bar\')\n+ r.writeFile("test.txt", b"foo")\n+ r.writeFile("foo.txt", b"bar")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.renameFile(\'/test.txt\', \'foo.txt\')\n+ info = view.renameFile("/test.txt", "foo.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'bar\')\n+ self.assertEqual(r.readFile("foo.txt"), b"bar")\n \n def test_delete(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n+ self.assertFalse("test.txt" in r)\n \n def test_delete_subfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.delete(\'/alpha/test.txt\')\n+ info = view.delete("/alpha/test.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r[\'alpha\'])\n+ self.assertFalse("test.txt" in r["alpha"])\n \n def test_delete_notfound(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n def test_move(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n- self.assertEqual(b\'foo\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertFalse("test.txt" in r)\n+ self.assertEqual(b"foo", r["alpha"].readFile("test.txt"))\n \n def test_move_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'bar\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"bar")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n- self.assertEqual(b\'bar\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertTrue("test.txt" in r)\n+ self.assertEqual(b"bar", r["alpha"].readFile("test.txt"))\n \n def test_move_invalid_parent(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n+ self.assertTrue("test.txt" in r)\n \n def test_download(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n- self.assertEqual(b\'foo\', view.download(\'/test.txt\'))\n+ view = FileManagerActions(r, self.layer["request"])\n+ self.assertEqual(b"foo", view.download("/test.txt"))\ndiff --git a/setup.py b/setup.py\nindex 8b4f192..758abb1 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -2,17 +2,13 @@\n from setuptools import setup\n \n \n-version = \'3.0.5.dev0\'\n+version = "3.0.5.dev0"\n \n setup(\n- name=\'plone.resourceeditor\',\n+ name="plone.resourceeditor",\n version=version,\n description="Integrates ACE editor into Plone",\n- long_description=(\n- open("README.rst").read() +\n- "\\n" +\n- open("CHANGES.rst").read()\n- ),\n+ long_description=(open("README.rst").read() + "\\n" + open("CHANGES.rst").read()),\n classifiers=[\n "Development Status :: 5 - Production/Stable",\n "Framework :: Plone",\n@@ -27,29 +23,27 @@\n "Programming Language :: Python :: 3.8",\n "Programming Language :: Python :: 3.9",\n "Programming Language :: Python :: 3.10",\n- ],\n- keywords=\'ace resource editor\',\n- author=\'Plone Foundation\',\n- author_email=\'plone-developers@lists.sourceforge.net\',\n- url=\'https://github.com/plone/plone.resourceeditor\',\n- license=\'GPL\',\n+ ],\n+ keywords="ace resource editor",\n+ author="Plone Foundation",\n+ author_email="plone-developers@lists.sourceforge.net",\n+ url="https://github.com/plone/plone.resourceeditor",\n+ license="GPL",\n packages=find_packages(),\n- namespace_packages=[\'plone\'],\n+ namespace_packages=["plone"],\n include_package_data=True,\n zip_safe=False,\n install_requires=[\n- \'plone.staticresources\',\n- \'setuptools\',\n- \'six\',\n- \'zope.component\',\n- \'zope.interface\',\n- \'zope.publisher\',\n- \'zope.schema\',\n- \'Zope2\',\n+ "plone.staticresources",\n+ "setuptools",\n+ "six",\n+ "zope.component",\n+ "zope.interface",\n+ "zope.publisher",\n+ "zope.schema",\n+ "Zope2",\n ],\n- extras_require={\n- \'test\': [\'plone.app.testing\']\n- },\n+ extras_require={"test": ["plone.app.testing"]},\n entry_points="""\n """,\n )\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:10:21+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/7e0a7930d1512ca783086819287f0c5582408f58 - -chore: zpretty - -Files changed: -M plone/resourceeditor/configure.zcml -M plone/resourceeditor/editor.pt -M plone/resourceeditor/preview.pt - -b'diff --git a/plone/resourceeditor/configure.zcml b/plone/resourceeditor/configure.zcml\nindex 3ea8f8d..84aaf76 100644\n--- a/plone/resourceeditor/configure.zcml\n+++ b/plone/resourceeditor/configure.zcml\n@@ -2,52 +2,53 @@\n xmlns="http://namespaces.zope.org/zope"\n xmlns:browser="http://namespaces.zope.org/browser"\n xmlns:zcml="http://namespaces.zope.org/zcml"\n- i18n_domain="plone">\n-\n- \n-\n- \n-\n- \n-\n- \n-\n- \n-\n- \n-\n- \n+ i18n_domain="plone"\n+ >\n+\n+ \n+\n+ \n+\n+ \n+\n+ \n+\n+ \n+\n+ \n+\n+ \n \n \ndiff --git a/plone/resourceeditor/editor.pt b/plone/resourceeditor/editor.pt\nindex 9ea1049..f7cfe66 100644\n--- a/plone/resourceeditor/editor.pt\n+++ b/plone/resourceeditor/editor.pt\n@@ -1,28 +1,45 @@\n-
\n+
\n \n- \n+ \n \n- \n- \n- \n+ \n+ \n+ \n \n- \n- \n- \n+ \n+ \n \n+ \n \n- \n+ \n \n- \n-
\n-
\n+ \n+
\n+
\n \n-
\n+
\n
\ndiff --git a/plone/resourceeditor/preview.pt b/plone/resourceeditor/preview.pt\nindex 60f5a0c..e522369 100644\n--- a/plone/resourceeditor/preview.pt\n+++ b/plone/resourceeditor/preview.pt\n@@ -1,16 +1,30 @@\n \n-
\n- \n-

\n- Date Modified: \n- n/a\n-
\n- Size: \n- n/a\n-

\n+ xmlns:metal="http://xml.zope.org/namespaces/metal"\n+ xmlns:tal="http://xml.zope.org/namespaces/tal"\n+ class="info"\n+ xml:lang="en"\n+ tal:define="\n+ info options/info;\n+ props info/properties;\n+ "\n+>\n+ \n+

\n+ Date Modified:\n+ \n+ n/a\n+
\n+ Size:\n+ \n+ n/a\n+

\n
\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:13:09+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/6d2755b1f6c6c87447b1b797eb5142a038cad6b6 - -chore: update trove classifiers - -Files changed: -M setup.py - -b'diff --git a/setup.py b/setup.py\nindex 758abb1..d5d406e 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -12,17 +12,14 @@\n classifiers=[\n "Development Status :: 5 - Production/Stable",\n "Framework :: Plone",\n- "Framework :: Plone :: 5.2",\n "Framework :: Plone :: 6.0",\n "Framework :: Plone :: Core",\n "License :: OSI Approved :: GNU General Public License (GPL)",\n "Programming Language :: Python",\n- "Programming Language :: Python :: 2.7",\n- "Programming Language :: Python :: 3.6",\n- "Programming Language :: Python :: 3.7",\n "Programming Language :: Python :: 3.8",\n "Programming Language :: Python :: 3.9",\n "Programming Language :: Python :: 3.10",\n+ "Programming Language :: Python :: 3.11",\n ],\n keywords="ace resource editor",\n author="Plone Foundation",\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:13:18+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/168446a23e9d56c570daa8197c29a1cd96883989 - -feat: pyroma - -Files changed: -M setup.py - -b'diff --git a/setup.py b/setup.py\nindex d5d406e..cf68c80 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -30,6 +30,7 @@\n namespace_packages=["plone"],\n include_package_data=True,\n zip_safe=False,\n+ python_requires=">=3.8",\n install_requires=[\n "plone.staticresources",\n "setuptools",\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:17:04+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/5b39a55b5589b5b756e85f64480c96885ecb2b74 - -feat: codespell - -Files changed: -M CHANGES.rst -M plone/resourceeditor/browser.py - -b'diff --git a/CHANGES.rst b/CHANGES.rst\nindex c2cd8c9..0921eae 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -111,7 +111,7 @@ Fixes:\n - Remove unittest2 dependency\n [kakshay21]\n - Split the error message for the move API endpoint into two. One\n- is for the parent folder and the other is for the distination folder\n+ is for the parent folder and the other is for the destination folder\n [b4oshany]\n - Fix Jenkins flake8 errors\n \n@@ -160,7 +160,7 @@ Fixes:\n 2.0.2 (2015-09-08)\n ------------------\n \n-- Added check to prevent overwritting folders when saving\n+- Added check to prevent overwriting folders when saving\n [obct537]\n \n 2.0.1 (2015-08-22)\ndiff --git a/plone/resourceeditor/browser.py b/plone/resourceeditor/browser.py\nindex 9d6366e..70f06b2 100644\n--- a/plone/resourceeditor/browser.py\n+++ b/plone/resourceeditor/browser.py\n@@ -1300,7 +1300,7 @@ def saveFile(self, path, value):\n path = path.lstrip("/")\n value = value.replace("\\r\\n", "\\n")\n self.context.writeFile(path, value)\n- return " " # Zope no likey empty responses\n+ return " " # Zope does not like empty responses\n \n def filetree(self):\n foldersOnly = bool(self.request.get("foldersOnly", False))\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:18:01+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/cf55508e0577bc0188432677e9e301d02dbabb6d - -feat: configure codespell - -Files changed: -M pyproject.toml - -b'diff --git a/pyproject.toml b/pyproject.toml\nindex 639f77a..860fd37 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -42,6 +42,8 @@ profile = "plone"\n [tool.black]\n target-version = ["py38"]\n \n+[tool.codespell]\n+ignore-words-list = "discreet"\n \n [tool.dependencychecker]\n Zope = [\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:23:28+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/7b257bbb3308e3113e5ecc70a184c83f6a68c8fe - -feat: drop six - -Files changed: -M plone/resourceeditor/browser.py -M plone/resourceeditor/tests/test_file_manager.py -M setup.py - -b'diff --git a/plone/resourceeditor/browser.py b/plone/resourceeditor/browser.py\nindex 70f06b2..b1b22cf 100644\n--- a/plone/resourceeditor/browser.py\n+++ b/plone/resourceeditor/browser.py\n@@ -6,10 +6,8 @@\n from plone.resource.file import FilesystemFile\n from plone.resource.interfaces import IResourceDirectory\n from Products.CMFCore.utils import getToolByName\n-from Products.CMFPlone.utils import safe_encode\n from Products.CMFPlone.utils import safe_unicode\n from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\n-from six.moves import urllib\n from time import localtime\n from time import strftime\n from urllib.parse import urlparse\n@@ -25,7 +23,7 @@\n import os.path\n import posixpath\n import re\n-import six\n+import urllib\n \n \n _ = MessageFactory("plone")\n@@ -83,9 +81,6 @@ def getFolder(self, path):\n to getFolder(). This can be used for example to only show image files\n in a file system tree.\n """\n- if six.PY2 and isinstance(path, str):\n- path = safe_encode(path, "utf-8")\n-\n folders = []\n files = []\n \n@@ -100,8 +95,6 @@ def getFolder(self, path):\n return folders + files\n \n def getFile(self, path):\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n path = self.normalizePath(path)\n ext = self.getExtension(path=path)\n result = {"ext": ext}\n@@ -117,10 +110,7 @@ def getFile(self, path):\n try:\n data = self.context.readFile(path)\n \n- if six.PY2 and isinstance(data, str):\n- result["contents"] = data.encode("utf8")\n- else:\n- result["contents"] = safe_unicode(data)\n+ result["contents"] = safe_unicode(data)\n try:\n return json.dumps(result)\n except UnicodeDecodeError:\n@@ -211,13 +201,7 @@ def getInfo(self, obj, path="/"):\n \n def saveFile(self, path, value):\n path = path.lstrip("/")\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- value = value.strip()\n- if six.PY2:\n- value = safe_encode(value, "utf-8")\n-\n- value = value.replace("\\r\\n", "\\n")\n+ value = value.strip().replace("\\r\\n", "\\n")\n \n if path in self.context:\n if IResourceDirectory.providedBy(self.context[path]):\n@@ -253,10 +237,6 @@ def saveFile(self, path, value):\n \n def addFolder(self, path, name):\n """Create a new directory on the server within the given path."""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- name = safe_encode(name, "utf-8")\n-\n code = 0\n error = ""\n \n@@ -312,10 +292,6 @@ def addFolder(self, path, name):\n \n def addFile(self, path, name):\n """Add a new empty file in the given directory"""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- name = safe_encode(name, "utf-8")\n-\n error = ""\n code = 0\n \n@@ -359,9 +335,6 @@ def addFile(self, path, name):\n \n def delete(self, path):\n """Delete the item at the given path."""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n-\n npath = self.normalizePath(path)\n parentPath = "/".join(npath.split("/")[:-1])\n name = npath.split("/")[-1]\n@@ -397,11 +370,6 @@ def delete(self, path):\n \n def renameFile(self, path, newName):\n """Rename the item at the given path to the new name"""\n-\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- newName = safe_encode(newName, "utf-8")\n-\n npath = self.normalizePath(path)\n oldPath = newPath = "/".join(npath.split("/")[:-1])\n oldName = npath.split("/")[-1]\n@@ -445,10 +413,6 @@ def renameFile(self, path, newName):\n \n def move(self, path, directory):\n """Move the item at the given path to a new directory"""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- directory = safe_encode(directory, "utf-8")\n-\n npath = self.normalizePath(path)\n newParentPath = self.normalizePath(directory)\n \n@@ -581,9 +545,6 @@ def getDirectory(folder, relpath=""):\n \n def download(self, path):\n """Serve the requested file to the user"""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n-\n npath = self.normalizePath(path)\n parentPath = "/".join(npath.split("/")[:-1])\n name = npath.split("/")[-1]\n@@ -821,9 +782,6 @@ def getFolder(self, path, getSizes=False):\n to getFolder(). This can be used for example to only show image files\n in a file system tree.\n """\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n-\n folders = []\n files = []\n \n@@ -844,9 +802,6 @@ def getInfo(self, path, getSize=False):\n indicates whether the dimensions of the file (if an image) should be\n returned.\n """\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n-\n path = self.normalizePath(path)\n obj = self.getObject(path)\n \n@@ -914,10 +869,6 @@ def getInfo(self, path, getSize=False):\n \n def addFolder(self, path, name):\n """Create a new directory on the server within the given path."""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- name = safe_encode(name, "utf-8")\n-\n code = 0\n error = ""\n \n@@ -978,21 +929,12 @@ def add(self, path, newfile, replacepath=None):\n uploaded file\'s name should be safe to use as a path component in a\n URL, so URL-encoded at a minimum.\n """\n-\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- if six.PY2 and replacepath is not None:\n- replacepath = safe_encode(replacepath, "utf-8")\n-\n parentPath = self.normalizePath(path)\n \n error = ""\n code = 0\n \n name = newfile.filename\n- if six.PY2 and isinstance(name, str):\n- name = safe_encode(name, "utf-8")\n-\n if replacepath:\n newPath = replacepath\n parentPath = "/".join(replacepath.split("/")[:-1])\n@@ -1037,10 +979,6 @@ def add(self, path, newfile, replacepath=None):\n \n def addNew(self, path, name):\n """Add a new empty file in the given directory"""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- name = safe_encode(name, "utf-8")\n-\n error = ""\n code = 0\n \n@@ -1080,10 +1018,6 @@ def addNew(self, path, name):\n \n def rename(self, path, newName):\n """Rename the item at the given path to the new name"""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- newName = safe_encode(newName, "utf-8")\n-\n npath = self.normalizePath(path)\n oldPath = newPath = "/".join(npath.split("/")[:-1])\n oldName = npath.split("/")[-1]\n@@ -1124,9 +1058,6 @@ def rename(self, path, newName):\n \n def delete(self, path):\n """Delete the item at the given path."""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n-\n npath = self.normalizePath(path)\n parentPath = "/".join(npath.split("/")[:-1])\n name = npath.split("/")[-1]\n@@ -1159,10 +1090,6 @@ def delete(self, path):\n \n def move(self, path, directory):\n """Move the item at the given path to a new directory"""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- directory = safe_encode(directory, "utf-8")\n-\n npath = self.normalizePath(path)\n newParentPath = self.normalizePath(directory)\n \n@@ -1229,9 +1156,6 @@ def move(self, path, directory):\n \n def download(self, path):\n """Serve the requested file to the user"""\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n-\n npath = self.normalizePath(path)\n parentPath = "/".join(npath.split("/")[:-1])\n name = npath.split("/")[-1]\n@@ -1275,9 +1199,6 @@ def getExtension(self, path, obj):\n # Methods that are their own views\n def getFile(self, path):\n self.setup()\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n-\n path = self.normalizePath(path)\n file = self.context.context.unrestrictedTraverse(path)\n ext = self.getExtension(path, file)\n@@ -1294,9 +1215,6 @@ def getFile(self, path):\n def saveFile(self, path, value):\n path = self.request.form.get("path", path)\n value = self.request.form.get("value", value)\n- if six.PY2:\n- path = safe_encode(path, "utf-8")\n- value = safe_encode(value, "utf-8")\n path = path.lstrip("/")\n value = value.replace("\\r\\n", "\\n")\n self.context.writeFile(path, value)\ndiff --git a/plone/resourceeditor/tests/test_file_manager.py b/plone/resourceeditor/tests/test_file_manager.py\nindex cc9b460..01f79c6 100644\n--- a/plone/resourceeditor/tests/test_file_manager.py\n+++ b/plone/resourceeditor/tests/test_file_manager.py\n@@ -1,6 +1,5 @@\n from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n \n-import six\n import unittest\n \n \ndiff --git a/setup.py b/setup.py\nindex cf68c80..73b7606 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -34,7 +34,6 @@\n install_requires=[\n "plone.staticresources",\n "setuptools",\n- "six",\n "zope.component",\n "zope.interface",\n "zope.publisher",\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:38:55+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/dee12e77a72c104414751c857cfd669cd00d6470 - -feat: drop CMFPlone dependency - -Files changed: -M plone/resourceeditor/browser.py - -b'diff --git a/plone/resourceeditor/browser.py b/plone/resourceeditor/browser.py\nindex b1b22cf..61d05c7 100644\n--- a/plone/resourceeditor/browser.py\n+++ b/plone/resourceeditor/browser.py\n@@ -2,11 +2,11 @@\n from DateTime import DateTime\n from OFS.Image import File\n from OFS.Image import Image\n+from plone.base.utils import safe_text\n from plone.resource.directory import FilesystemResourceDirectory\n from plone.resource.file import FilesystemFile\n from plone.resource.interfaces import IResourceDirectory\n from Products.CMFCore.utils import getToolByName\n-from Products.CMFPlone.utils import safe_unicode\n from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\n from time import localtime\n from time import strftime\n@@ -110,7 +110,7 @@ def getFile(self, path):\n try:\n data = self.context.readFile(path)\n \n- result["contents"] = safe_unicode(data)\n+ result["contents"] = safe_text(data)\n try:\n return json.dumps(result)\n except UnicodeDecodeError:\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:38:56+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/f50f1ac161f8cab00786afbc3a2030659e965f47 - -feat: declare dependencies - -Files changed: -M setup.py - -b'diff --git a/setup.py b/setup.py\nindex 73b7606..e529d5c 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -32,13 +32,12 @@\n zip_safe=False,\n python_requires=">=3.8",\n install_requires=[\n+ "plone.base",\n+ "plone.resource",\n "plone.staticresources",\n "setuptools",\n- "zope.component",\n- "zope.interface",\n- "zope.publisher",\n- "zope.schema",\n- "Zope2",\n+ "Products.CMFCore",\n+ "Zope",\n ],\n extras_require={"test": ["plone.app.testing"]},\n entry_points="""\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:40:11+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/600a4296559573e2643007c2115068853d59d095 - -chore: bump version - -Files changed: -M setup.py - -b'diff --git a/setup.py b/setup.py\nindex e529d5c..50da757 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -2,7 +2,7 @@\n from setuptools import setup\n \n \n-version = "3.0.5.dev0"\n+version = "4.0.0.dev0"\n \n setup(\n name="plone.resourceeditor",\n' - -Repository: plone.resourceeditor - - -Branch: refs/heads/master -Date: 2023-04-25T16:40:39+02:00 -Author: Gil Forcada Codinachs (gforcada) -Commit: https://github.com/plone/plone.resourceeditor/commit/f9f858e39f738008f81954c11821842d4f9c7ecc +This time is set since Plone 6.0.4. +Fixes https://github.com/plone/plone.app.caching/issues/93. -Add news entry +This removes the previous code which read `timestamp.txt` from `/portal_resources/resource_overrides/production`. +This timestamp is no longer set in Plone 6, and does not influence anything. Files changed: -A news/1.breaking +A news/93.feature +M plone/app/caching/operations/etags.py +M plone/app/caching/tests/test_profile_with_caching_proxy.py +M plone/app/caching/tests/test_profile_without_caching_proxy.py -b'diff --git a/news/1.breaking b/news/1.breaking\nnew file mode 100644\nindex 0000000..6a2e7d2\n--- /dev/null\n+++ b/news/1.breaking\n@@ -0,0 +1,2 @@\n+Drop python 2.7 compatibility.\n+[gforcada]\n' +b'diff --git a/news/93.feature b/news/93.feature\nnew file mode 100644\nindex 0000000..fe0847c\n--- /dev/null\n+++ b/news/93.feature\n@@ -0,0 +1,4 @@\n+Update the resourceRegistries ETag to use the config registry modification time.\n+This time is set since Plone 6.0.4.\n+Fixes `issue 93 `_.\n+[maurits]\ndiff --git a/plone/app/caching/operations/etags.py b/plone/app/caching/operations/etags.py\nindex 8187172..20f1783 100644\n--- a/plone/app/caching/operations/etags.py\n+++ b/plone/app/caching/operations/etags.py\n@@ -4,11 +4,10 @@\n from plone.app.caching.operations.utils import getContext\n from plone.app.caching.operations.utils import getLastModifiedAnnotation\n from plone.base.utils import safe_hasattr\n+from plone.registry.interfaces import IRegistry\n from Products.CMFCore.interfaces import ICatalogTool\n from Products.CMFCore.interfaces import IMembershipTool\n from Products.CMFCore.utils import getToolByName\n-from Products.CMFPlone.resources.utils import get_override_directory\n-from Products.CMFPlone.resources.utils import PRODUCTION_RESOURCE_DIRECTORY\n from zope.component import adapter\n from zope.component import queryMultiAdapter\n from zope.component import queryUtility\n@@ -19,6 +18,13 @@\n import time\n \n \n+try:\n+ # available since Plone 6.0.4\n+ from Products.CMFPlone.resources.browser.resource import _RESOURCE_REGISTRY_MTIME\n+except ImportError:\n+ _RESOURCE_REGISTRY_MTIME = None\n+\n+\n @implementer(IETagValue)\n @adapter(Interface, Interface)\n class UserID:\n@@ -239,22 +245,15 @@ def __init__(self, published, request):\n self.request = request\n \n def __call__(self):\n- context = getContext(self.published)\n- container = get_override_directory(context)\n- if PRODUCTION_RESOURCE_DIRECTORY not in container:\n+ if _RESOURCE_REGISTRY_MTIME is None:\n return ""\n- production_folder = container[PRODUCTION_RESOURCE_DIRECTORY]\n- filename = "timestamp.txt"\n- if filename not in production_folder:\n+ registry = queryUtility(IRegistry)\n+ if registry is None:\n return ""\n- timestamp = production_folder.readFile(filename)\n- if not timestamp:\n+ mtime = getattr(registry, _RESOURCE_REGISTRY_MTIME, None)\n+ if mtime is None:\n return ""\n- # timestamp is in bytes, and we must return a string.\n- # On Python 2 this is the same, but not on Python 3.\n- if not isinstance(timestamp, str):\n- timestamp = timestamp.decode("utf-8")\n- return timestamp\n+ return str(mtime)\n \n \n @implementer(IETagValue)\ndiff --git a/plone/app/caching/tests/test_profile_with_caching_proxy.py b/plone/app/caching/tests/test_profile_with_caching_proxy.py\nindex 8df999a..cdb62c9 100644\n--- a/plone/app/caching/tests/test_profile_with_caching_proxy.py\n+++ b/plone/app/caching/tests/test_profile_with_caching_proxy.py\n@@ -73,12 +73,12 @@ def test_composite_viewsxx(self):\n # Can we just call that test from this context?\n \n catalog = self.portal["portal_catalog"]\n- skins_tool = self.portal["portal_skins"]\n+ default_skin = self.portal["portal_skins"].default_skin\n \n # Add folder content\n setRoles(self.portal, TEST_USER_ID, ("Manager",))\n self.portal.invokeFactory("Folder", "f1")\n- self.portal["f1"].title = "one"\n+ self.portal["f1"].title = "Folder one"\n self.portal["f1"].description = "Folder one description"\n self.portal["f1"].reindexObject()\n \n@@ -105,9 +105,16 @@ def test_composite_viewsxx(self):\n # - turn on gzip?\n # - set skin? Maybe\n # - leave status unlocked\n- #\n+ # - set the mod date on the resource registries? Probably.\n transaction.commit()\n \n+ # Since Plone 6.0.4 we have a modification date on the registry.\n+ from Products.CMFPlone.resources.browser.resource import (\n+ _RESOURCE_REGISTRY_MTIME,\n+ )\n+\n+ mtime = str(getattr(self.registry, _RESOURCE_REGISTRY_MTIME))\n+\n # Request the authenticated folder\n now = stable_now()\n browser = Browser(self.app)\n@@ -124,10 +131,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"|test_user_1_|{catalog.getCounter()}|en|{skins_tool.default_skin}|0|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Set the copy/cut cookie and then request the folder view again\n@@ -141,10 +146,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"|test_user_1_|{catalog.getCounter()}|en|{skins_tool.default_skin}|0|1|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|1|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n \n # Request the authenticated page\n now = stable_now()\n@@ -163,10 +166,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"|test_user_1_|{catalog.getCounter()}|en|{skins_tool.default_skin}|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the authenticated page again -- to test RAM cache.\n@@ -205,7 +206,6 @@ def test_composite_viewsxx(self):\n # Request the anonymous folder\n now = stable_now()\n browser = Browser(self.app)\n- browser.handleErrors = False\n browser.open(self.portal["f1"].absolute_url())\n self.assertEqual("plone.content.folderView", browser.headers["X-Cache-Rule"])\n self.assertEqual(\n@@ -215,10 +215,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"||{catalog.getCounter()}|en|{skins_tool.default_skin}|0|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the anonymous page\n@@ -234,10 +232,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"||{catalog.getCounter()}|en|{skins_tool.default_skin}|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the anonymous page again -- to test RAM cache.\n@@ -257,10 +253,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"||{catalog.getCounter()}|en|{skins_tool.default_skin}|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the anonymous page again -- with an INM header to test 304.\ndiff --git a/plone/app/caching/tests/test_profile_without_caching_proxy.py b/plone/app/caching/tests/test_profile_without_caching_proxy.py\nindex 07a0585..3d9076a 100644\n--- a/plone/app/caching/tests/test_profile_without_caching_proxy.py\n+++ b/plone/app/caching/tests/test_profile_without_caching_proxy.py\n@@ -92,6 +92,13 @@ def test_composite_views(self):\n # - set the mod date on the resource registries? Probably.\n transaction.commit()\n \n+ # Since Plone 6.0.4 we have a modification date on the registry.\n+ from Products.CMFPlone.resources.browser.resource import (\n+ _RESOURCE_REGISTRY_MTIME,\n+ )\n+\n+ mtime = str(getattr(self.registry, _RESOURCE_REGISTRY_MTIME))\n+\n # Request the authenticated folder\n now = stable_now()\n browser = Browser(self.app)\n@@ -108,7 +115,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|0|"\'\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -123,7 +130,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|1|"\'\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|1|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n \n # Request the authenticated page\n@@ -143,7 +150,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|"\'\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -192,7 +199,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|0|"\'\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -209,7 +216,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|"\'\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -230,7 +237,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|"\'\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n' -Repository: plone.resourceeditor +Repository: plone.app.caching Branch: refs/heads/master -Date: 2023-04-26T08:01:58+02:00 +Date: 2023-04-26T08:25:51+02:00 Author: Jens W. Klein (jensens) -Commit: https://github.com/plone/plone.resourceeditor/commit/4500f85c3e8470c2dc273f78c43a3fa3197736fc +Commit: https://github.com/plone/plone.app.caching/commit/4cdf6fb83bf110f93c8e1736225049695d391e38 -Merge pull request #31 from plone/config-with-default-template-e43c6e11 +Merge pull request #125 from plone/maurits-rr-etag -Config with default template +Update resourceRegistries ETag to use the config registry mtime Files changed: -A .editorconfig -A .meta.toml -A .pre-commit-config.yaml -A news/1.breaking -A news/2a4ba395.internal -A tox.ini -M CHANGES.rst -M plone/__init__.py -M plone/resourceeditor/__init__.py -M plone/resourceeditor/browser.py -M plone/resourceeditor/configure.zcml -M plone/resourceeditor/editor.pt -M plone/resourceeditor/preview.pt -M plone/resourceeditor/testing.py -M plone/resourceeditor/tests/test_file_manager.py -M plone/resourceeditor/tests/test_file_manager_action.py -M pyproject.toml -M setup.cfg -M setup.py +A news/93.feature +M plone/app/caching/operations/etags.py +M plone/app/caching/tests/test_profile_with_caching_proxy.py +M plone/app/caching/tests/test_profile_without_caching_proxy.py -b'diff --git a/.editorconfig b/.editorconfig\nnew file mode 100644\nindex 0000000..b4158b8\n--- /dev/null\n+++ b/.editorconfig\n@@ -0,0 +1,39 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+#\n+# EditorConfig Configuration file, for more details see:\n+# http://EditorConfig.org\n+# EditorConfig is a convention description, that could be interpreted\n+# by multiple editors to enforce common coding conventions for specific\n+# file types\n+\n+# top-most EditorConfig file:\n+# Will ignore other EditorConfig files in Home directory or upper tree level.\n+root = true\n+\n+\n+[*] # For All Files\n+# Unix-style newlines with a newline ending every file\n+end_of_line = lf\n+insert_final_newline = true\n+trim_trailing_whitespace = true\n+# Set default charset\n+charset = utf-8\n+# Indent style default\n+indent_style = space\n+# Max Line Length - a hard line wrap, should be disabled\n+max_line_length = off\n+\n+[*.{py,cfg,ini}]\n+# 4 space indentation\n+indent_size = 4\n+\n+[*.{yml,zpt,pt,dtml,zcml}]\n+# 2 space indentation\n+indent_size = 2\n+\n+[{Makefile,.gitmodules}]\n+# Tab indentation (no size specified, but view as 4 spaces)\n+indent_style = tab\n+indent_size = unset\n+tab_width = unset\ndiff --git a/.meta.toml b/.meta.toml\nnew file mode 100644\nindex 0000000..99342b2\n--- /dev/null\n+++ b/.meta.toml\n@@ -0,0 +1,5 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[meta]\n+template = "default"\n+commit-id = "2a4ba395"\ndiff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml\nnew file mode 100644\nindex 0000000..fdafec1\n--- /dev/null\n+++ b/.pre-commit-config.yaml\n@@ -0,0 +1,42 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+ci:\n+ autofix_prs: false\n+ autoupdate_schedule: monthly\n+\n+repos:\n+- repo: https://github.com/asottile/pyupgrade\n+ rev: v3.3.1\n+ hooks:\n+ - id: pyupgrade\n+ args: [--py38-plus]\n+- repo: https://github.com/pycqa/isort\n+ rev: 5.12.0\n+ hooks:\n+ - id: isort\n+- repo: https://github.com/psf/black\n+ rev: 23.3.0\n+ hooks:\n+ - id: black\n+- repo: https://github.com/collective/zpretty\n+ rev: 3.0.3\n+ hooks:\n+ - id: zpretty\n+- repo: https://github.com/PyCQA/flake8\n+ rev: 6.0.0\n+ hooks:\n+ - id: flake8\n+- repo: https://github.com/codespell-project/codespell\n+ rev: v2.2.4\n+ hooks:\n+ - id: codespell\n+ additional_dependencies:\n+ - tomli\n+- repo: https://github.com/mgedmin/check-manifest\n+ rev: "0.49"\n+ hooks:\n+ - id: check-manifest\n+- repo: https://github.com/regebro/pyroma\n+ rev: "4.2"\n+ hooks:\n+ - id: pyroma\ndiff --git a/CHANGES.rst b/CHANGES.rst\nindex c2cd8c9..0921eae 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -111,7 +111,7 @@ Fixes:\n - Remove unittest2 dependency\n [kakshay21]\n - Split the error message for the move API endpoint into two. One\n- is for the parent folder and the other is for the distination folder\n+ is for the parent folder and the other is for the destination folder\n [b4oshany]\n - Fix Jenkins flake8 errors\n \n@@ -160,7 +160,7 @@ Fixes:\n 2.0.2 (2015-09-08)\n ------------------\n \n-- Added check to prevent overwritting folders when saving\n+- Added check to prevent overwriting folders when saving\n [obct537]\n \n 2.0.1 (2015-08-22)\ndiff --git a/news/1.breaking b/news/1.breaking\nnew file mode 100644\nindex 0000000..6a2e7d2\n--- /dev/null\n+++ b/news/1.breaking\n@@ -0,0 +1,2 @@\n+Drop python 2.7 compatibility.\n+[gforcada]\ndiff --git a/news/2a4ba395.internal b/news/2a4ba395.internal\nnew file mode 100644\nindex 0000000..c08f539\n--- /dev/null\n+++ b/news/2a4ba395.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone devs]\ndiff --git a/plone/__init__.py b/plone/__init__.py\nindex 68c04af..5284146 100644\n--- a/plone/__init__.py\n+++ b/plone/__init__.py\n@@ -1,2 +1 @@\n-# -*- coding: utf-8 -*-\n-__import__(\'pkg_resources\').declare_namespace(__name__)\n+__import__("pkg_resources").declare_namespace(__name__)\ndiff --git a/plone/resourceeditor/__init__.py b/plone/resourceeditor/__init__.py\nindex a4e0266..3447c9b 100644\n--- a/plone/resourceeditor/__init__.py\n+++ b/plone/resourceeditor/__init__.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n import mimetypes\n import os.path\n \n@@ -18,4 +17,4 @@ def add_files(filenames):\n \n \n here = os.path.dirname(os.path.abspath(__file__))\n-add_files([os.path.join(here, \'mime.types\')])\n+add_files([os.path.join(here, "mime.types")])\ndiff --git a/plone/resourceeditor/browser.py b/plone/resourceeditor/browser.py\nindex 9d6a0fe..61d05c7 100644\n--- a/plone/resourceeditor/browser.py\n+++ b/plone/resourceeditor/browser.py\n@@ -1,19 +1,16 @@\n-# -*- coding: utf-8 -*-\n from AccessControl import Unauthorized\n from DateTime import DateTime\n from OFS.Image import File\n from OFS.Image import Image\n+from plone.base.utils import safe_text\n from plone.resource.directory import FilesystemResourceDirectory\n from plone.resource.file import FilesystemFile\n from plone.resource.interfaces import IResourceDirectory\n from Products.CMFCore.utils import getToolByName\n-from Products.CMFPlone.utils import safe_unicode\n-from Products.CMFPlone.utils import safe_encode\n from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\n-from six.moves import urllib\n-from six.moves.urllib.parse import urlparse\n from time import localtime\n from time import strftime\n+from urllib.parse import urlparse\n from zExceptions import NotFound\n from zope.cachedescriptors import property as zproperty\n from zope.component import queryMultiAdapter\n@@ -26,15 +23,14 @@\n import os.path\n import posixpath\n import re\n-import six\n+import urllib\n \n \n-_ = MessageFactory(u\'plone\')\n+_ = MessageFactory("plone")\n \n \n def authorize(context, request):\n- authenticator = queryMultiAdapter((context, request),\n- name=u\'authenticator\')\n+ authenticator = queryMultiAdapter((context, request), name="authenticator")\n if authenticator is not None and not authenticator.verify():\n raise Unauthorized\n \n@@ -47,9 +43,8 @@ def validateFilename(name):\n \n \n class FileManagerActions(BrowserView):\n-\n- imageExtensions = [\'png\', \'gif\', \'jpg\', \'jpeg\', \'ico\']\n- previewTemplate = ViewPageTemplateFile(\'preview.pt\')\n+ imageExtensions = ["png", "gif", "jpg", "jpeg", "ico"]\n+ previewTemplate = ViewPageTemplateFile("preview.pt")\n \n @zproperty.Lazy\n def resourceDirectory(self):\n@@ -61,7 +56,7 @@ def getObject(self, path):\n return self.resourceDirectory\n try:\n return self.resourceDirectory[path]\n- except (KeyError, NotFound,):\n+ except (KeyError, NotFound):\n raise KeyError(path)\n \n def getExtension(self, obj=None, path=None):\n@@ -86,9 +81,6 @@ def getFolder(self, path):\n to getFolder(). This can be used for example to only show image files\n in a file system tree.\n """\n- if six.PY2 and isinstance(path, six.text_type):\n- path = safe_encode(path, \'utf-8\')\n-\n folders = []\n files = []\n \n@@ -97,70 +89,59 @@ def getFolder(self, path):\n \n for name in folder.listDirectory():\n if IResourceDirectory.providedBy(folder[name]):\n- folders.append(self.getInfo(\n- folder[name],\n- path=\'/{0}/{1}/\'.format(path, name)\n- ))\n+ folders.append(self.getInfo(folder[name], path=f"/{path}/{name}/"))\n else:\n- files.append(self.getInfo(\n- folder[name],\n- path=\'/{0}/{1}\'.format(path, name)\n- ))\n+ files.append(self.getInfo(folder[name], path=f"/{path}/{name}"))\n return folders + files\n \n def getFile(self, path):\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n path = self.normalizePath(path)\n ext = self.getExtension(path=path)\n- result = {\'ext\': ext}\n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n+ result = {"ext": ext}\n+ self.request.response.setHeader("Content-Type", "application/json")\n \n if ext in self.imageExtensions:\n obj = self.getObject(path)\n info = self.getInfo(obj)\n- info[\'preview\'] = path\n- result[\'info\'] = self.previewTemplate(info=info)\n+ info["preview"] = path\n+ result["info"] = self.previewTemplate(info=info)\n return json.dumps(result)\n else:\n try:\n data = self.context.readFile(path)\n \n- if six.PY2 and isinstance(data, six.text_type):\n- result[\'contents\'] = data.encode(\'utf8\')\n- else:\n- result[\'contents\'] = safe_unicode(data)\n+ result["contents"] = safe_text(data)\n try:\n return json.dumps(result)\n except UnicodeDecodeError:\n # The file we\'re trying to get isn\'t unicode encodable\n # so we just return the file information, not the content\n- del result[\'contents\']\n+ del result["contents"]\n obj = self.getObject(path)\n info = self.getInfo(obj)\n- result[\'info\'] = self.previewTemplate(info=info)\n+ result["info"] = self.previewTemplate(info=info)\n return json.dumps(result)\n except AttributeError:\n return None\n \n def normalizePath(self, path):\n- if path.startswith(\'/\'):\n+ if path.startswith("/"):\n path = path[1:]\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n return path\n \n def normalizeReturnPath(self, path):\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n- if not path.startswith(\'/\'):\n- path = \'/\' + path\n+ if not path.startswith("/"):\n+ path = "/" + path\n return path\n \n def parentPath(self, path):\n- return \'/\'.join(path.split(\'/\')[:-1])\n+ return "/".join(path.split("/")[:-1])\n \n- def getInfo(self, obj, path=\'/\'):\n+ def getInfo(self, obj, path="/"):\n """Returns information about a single file. Requests\n with mode "getinfo" will include an additional parameter, "path",\n indicating which file to inspect. A boolean parameter "getsize"\n@@ -170,17 +151,17 @@ def getInfo(self, obj, path=\'/\'):\n filename = obj.__name__\n \n properties = {\n- \'dateModified\': None,\n+ "dateModified": None,\n }\n \n size = 0\n \n if isinstance(obj, File):\n- properties[\'dateModified\'] = DateTime(obj._p_mtime).strftime(\'%c\')\n+ properties["dateModified"] = DateTime(obj._p_mtime).strftime("%c")\n size = obj.get_size() / 1024\n \n if IResourceDirectory.providedBy(obj):\n- fileType = \'dir\'\n+ fileType = "dir"\n is_folder = True\n else:\n fileType = self.getExtension(obj)\n@@ -188,55 +169,51 @@ def getInfo(self, obj, path=\'/\'):\n if isinstance(obj, FilesystemFile):\n stats = os.stat(obj.path)\n modified = localtime(stats.st_mtime)\n- properties[\'dateModified\'] = strftime(\'%c\', modified)\n+ properties["dateModified"] = strftime("%c", modified)\n size = stats.st_size / 1024\n \n if size < 1024:\n- size_specifier = u\'kb\'\n+ size_specifier = "kb"\n else:\n- size_specifier = u\'mb\'\n+ size_specifier = "mb"\n size = size / 1024\n- properties[\'size\'] = \'{0}{1}\'.format(\n+ properties["size"] = "{}{}".format(\n size,\n- translate(_(u\'filemanager_{0}\'.format(size_specifier),\n- default=size_specifier), context=self.request)\n+ translate(\n+ _(f"filemanager_{size_specifier}", default=size_specifier),\n+ context=self.request,\n+ ),\n )\n \n if isinstance(obj, Image):\n- properties[\'height\'] = obj.height\n- properties[\'width\'] = obj.width\n+ properties["height"] = obj.height\n+ properties["width"] = obj.width\n \n return {\n- \'filename\': filename,\n- \'label\': filename,\n- \'fileType\': fileType,\n- \'filesystem\': isinstance(obj, FilesystemFile),\n- \'properties\': properties,\n- \'path\': path,\n- \'folder\': is_folder\n+ "filename": filename,\n+ "label": filename,\n+ "fileType": fileType,\n+ "filesystem": isinstance(obj, FilesystemFile),\n+ "properties": properties,\n+ "path": path,\n+ "folder": is_folder,\n }\n \n def saveFile(self, path, value):\n- path = path.lstrip(\'/\')\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- value = value.strip()\n- if six.PY2:\n- value = safe_encode(value, \'utf-8\')\n-\n- value = value.replace(\'\\r\\n\', \'\\n\')\n+ path = path.lstrip("/")\n+ value = value.strip().replace("\\r\\n", "\\n")\n \n if path in self.context:\n if IResourceDirectory.providedBy(self.context[path]):\n- return json.dumps({\'error\': \'invalid path\'})\n+ return json.dumps({"error": "invalid path"})\n \n- if \'relativeUrls\' in self.request.form:\n- reg = re.compile(r\'url\\(([^)]+)\\)\')\n+ if "relativeUrls" in self.request.form:\n+ reg = re.compile(r"url\\(([^)]+)\\)")\n urls = reg.findall(value)\n \n # Trim off the @@plone.resourceeditor bit to just give us the\n # theme url\n- limit = self.request.URL.find(\'@@plone.resourceeditor\')\n+ limit = self.request.URL.find("@@plone.resourceeditor")\n location = self.request.URL[0:limit]\n base = urlparse(location)\n for url in urls:\n@@ -244,29 +221,24 @@ def saveFile(self, path, value):\n if base.netloc != asset.netloc:\n continue\n \n- base_dir = \'.\' + posixpath.dirname(base.path)\n- target = \'.\' + asset.path\n+ base_dir = "." + posixpath.dirname(base.path)\n+ target = "." + asset.path\n out = posixpath.relpath(target, start=base_dir)\n value = value.replace(url.strip(\'"\').strip("\'"), out)\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n+ self.request.response.setHeader("Content-Type", "application/json")\n if isinstance(self.context, FilesystemResourceDirectory):\n # we cannot save in an FS directory, but we return the file content\n # (useful when we compile less from the theming editor)\n- return json.dumps({\'success\': \'tmp\', \'value\': value})\n+ return json.dumps({"success": "tmp", "value": value})\n else:\n self.context.writeFile(path, value)\n- return json.dumps({\'success\': \'save\'})\n+ return json.dumps({"success": "save"})\n \n def addFolder(self, path, name):\n- """Create a new directory on the server within the given path.\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n-\n+ """Create a new directory on the server within the given path."""\n code = 0\n- error = \'\'\n+ error = ""\n \n parentPath = self.normalizePath(path)\n parent = None\n@@ -274,20 +246,26 @@ def addFolder(self, path, name):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_foldername", default="Invalid folder name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Folder already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Folder already exists.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n@@ -295,400 +273,435 @@ def addFolder(self, path, name):\n except UnicodeDecodeError:\n error = translate(\n _(\n- u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'\n+ "filemanager_invalid_foldername",\n+ default="Invalid folder name.",\n ),\n- context=self.request\n+ context=self.request,\n )\n code = 1\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n- })\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n+ }\n+ )\n \n def addFile(self, path, name):\n- """Add a new empty file in the given directory\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n-\n- error = \'\'\n+ """Add a new empty file in the given directory"""\n+ error = ""\n code = 0\n \n parentPath = self.normalizePath(path)\n- newPath = \'{0}/{1}\'.format(parentPath, name,)\n+ newPath = f"{parentPath}/{name}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_filename\',\n- default=u\'Invalid file name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_filename", default="Invalid file name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n- self.resourceDirectory.writeFile(newPath, b\'\')\n-\n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n- \'path\': path\n- })\n+ self.resourceDirectory.writeFile(newPath, b"")\n+\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n+ "path": path,\n+ }\n+ )\n \n def delete(self, path):\n- """Delete the item at the given path.\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n-\n+ """Delete the item at the given path."""\n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n del parent[name]\n except KeyError:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'path\': self.normalizeReturnPath(path),\n- \'error\': error,\n- \'code\': code,\n- })\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "path": self.normalizeReturnPath(path),\n+ "error": error,\n+ "code": code,\n+ }\n+ )\n \n def renameFile(self, path, newName):\n- """Rename the item at the given path to the new name\n- """\n-\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- newName = safe_encode(newName, \'utf-8\')\n-\n+ """Rename the item at the given path to the new name"""\n npath = self.normalizePath(path)\n- oldPath = newPath = \'/\'.join(npath.split(\'/\')[:-1])\n- oldName = npath.split(\'/\')[-1]\n+ oldPath = newPath = "/".join(npath.split("/")[:-1])\n+ oldName = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(oldPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if newName != oldName:\n if newName in parent:\n error = translate(\n _(\n- u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'\n+ "filemanager_error_file_exists",\n+ default="File already exists.",\n ),\n- context=self.request)\n+ context=self.request,\n+ )\n code = 1\n else:\n parent.rename(oldName, newName)\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n- return json.dumps({\n- \'oldParent\': self.normalizeReturnPath(oldPath),\n- \'oldName\': oldName,\n- \'newParent\': self.normalizeReturnPath(newPath),\n- \'newName\': newName,\n- \'error\': error,\n- \'code\': code,\n- })\n+ self.request.response.setHeader("Content-Type", "application/json")\n+ return json.dumps(\n+ {\n+ "oldParent": self.normalizeReturnPath(oldPath),\n+ "oldName": oldName,\n+ "newParent": self.normalizeReturnPath(newPath),\n+ "newName": newName,\n+ "error": error,\n+ "code": code,\n+ }\n+ )\n \n def move(self, path, directory):\n- """Move the item at the given path to a new directory\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- directory = safe_encode(directory, \'utf-8\')\n-\n+ """Move the item at the given path to a new directory"""\n npath = self.normalizePath(path)\n newParentPath = self.normalizePath(directory)\n \n parentPath = self.parentPath(npath)\n- filename = npath.split(\'/\')[-1]\n+ filename = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n- newCanonicalPath = \'{0}/{1}\'.format(newParentPath, filename)\n+ error = ""\n+ newCanonicalPath = f"{newParentPath}/{filename}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n- return json.dumps({\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n- })\n+ return json.dumps(\n+ {\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n+ }\n+ )\n \n try:\n target = self.getObject(newParentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Destination folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Destination folder not found.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n- return json.dumps({\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n- })\n+ return json.dumps(\n+ {\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n+ }\n+ )\n \n if filename not in parent:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n elif filename in target:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n obj = parent[filename]\n del parent[filename]\n target[filename] = obj\n \n- return json.dumps({\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n- })\n+ return json.dumps(\n+ {\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n+ }\n+ )\n \n def do_action(self, action):\n- if action == \'dataTree\':\n+ if action == "dataTree":\n \n- def getDirectory(folder, relpath=\'\'):\n+ def getDirectory(folder, relpath=""):\n items = []\n for name in folder.listDirectory():\n obj = folder[name]\n- path = relpath + \'/\' + name\n+ path = relpath + "/" + name\n if IResourceDirectory.providedBy(obj):\n try:\n children = getDirectory(obj, path)\n except NotFound:\n children = []\n- items.append({\n- \'label\': name,\n- \'folder\': True,\n- \'path\': path,\n- \'children\': children\n- })\n+ items.append(\n+ {\n+ "label": name,\n+ "folder": True,\n+ "path": path,\n+ "children": children,\n+ }\n+ )\n else:\n items.append(self.getInfo(obj, path))\n return items\n \n return json.dumps(getDirectory(self.context))\n \n- if action == \'getFile\':\n- path = self.request.get(\'path\', \'\')\n+ if action == "getFile":\n+ path = self.request.get("path", "")\n return self.getFile(path)\n \n- if action == \'saveFile\':\n- path = self.request.get(\'path\', \'\')\n- data = self.request.get(\'data\', \'\')\n+ if action == "saveFile":\n+ path = self.request.get("path", "")\n+ data = self.request.get("data", "")\n return self.saveFile(path, data)\n \n- if action == \'addFolder\':\n- path = self.request.get(\'path\', \'\')\n- name = self.request.get(\'name\', \'\')\n+ if action == "addFolder":\n+ path = self.request.get("path", "")\n+ name = self.request.get("name", "")\n return self.addFolder(path, name)\n \n- if action == \'addFile\':\n- path = self.request.get(\'path\', \'\')\n- name = self.request.get(\'filename\', \'\')\n+ if action == "addFile":\n+ path = self.request.get("path", "")\n+ name = self.request.get("filename", "")\n return self.addFile(path, name)\n \n- if action == \'renameFile\':\n- path = self.request.get(\'path\', \'\')\n- name = self.request.get(\'filename\', \'\')\n+ if action == "renameFile":\n+ path = self.request.get("path", "")\n+ name = self.request.get("filename", "")\n return self.renameFile(path, name)\n \n- if action == \'delete\':\n- path = self.request.get(\'path\', \'\')\n+ if action == "delete":\n+ path = self.request.get("path", "")\n return self.delete(path)\n \n- if action == \'move\':\n- src_path = self.request.get(\'source\', \'\')\n- des_path = self.request.get(\'destination\', \'\')\n+ if action == "move":\n+ src_path = self.request.get("source", "")\n+ des_path = self.request.get("destination", "")\n return self.move(src_path, des_path)\n \n def download(self, path):\n- """Serve the requested file to the user\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n-\n+ """Serve the requested file to the user"""\n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n \n parent = self.getObject(parentPath)\n \n- self.request.response.setHeader(\'Content-Type\',\n- \'application/octet-stream\')\n+ self.request.response.setHeader("Content-Type", "application/octet-stream")\n self.request.response.setHeader(\n- \'Content-Disposition\',\n- \'attachment; filename="{0}"\'.format(name)\n+ "Content-Disposition", f\'attachment; filename="{name}"\'\n )\n \n # TODO: Use streams here if we can\n return parent.readFile(name)\n \n def __call__(self):\n- action = self.request.get(\'action\')\n+ action = self.request.get("action")\n return self.do_action(action)\n \n \n class FileManager(BrowserView):\n- """Render the file manager and support its AJAX requests.\n- """\n-\n- previewTemplate = ViewPageTemplateFile(\'preview.pt\')\n- staticFiles = \'++resource++plone.resourceeditor/filemanager\'\n- imageExtensions = [\'png\', \'gif\', \'jpg\', \'jpeg\']\n- knownExtensions = [\'css\', \'html\', \'htm\', \'txt\', \'xml\', \'js\', \'cfg\']\n- capabilities = [\'download\', \'rename\', \'delete\']\n-\n- extensionsWithIcons = frozenset([\n- \'aac\', \'avi\', \'bmp\', \'chm\', \'css\', \'dll\', \'doc\', \'fla\',\n- \'gif\', \'htm\', \'html\', \'ini\', \'jar\', \'jpeg\', \'jpg\', \'js\',\n- \'lasso\', \'mdb\', \'mov\', \'mp3\', \'mpg\', \'pdf\', \'php\', \'png\',\n- \'ppt\', \'py\', \'rb\', \'real\', \'reg\', \'rtf\', \'sql\', \'swf\', \'txt\',\n- \'vbs\', \'wav\', \'wma\', \'wmv\', \'xls\', \'xml\', \'xsl\', \'zip\',\n- ])\n-\n- protectedActions = (\n- \'addfolder\', \'add\', \'addnew\',\n- \'rename\', \'delete\'\n+ """Render the file manager and support its AJAX requests."""\n+\n+ previewTemplate = ViewPageTemplateFile("preview.pt")\n+ staticFiles = "++resource++plone.resourceeditor/filemanager"\n+ imageExtensions = ["png", "gif", "jpg", "jpeg"]\n+ knownExtensions = ["css", "html", "htm", "txt", "xml", "js", "cfg"]\n+ capabilities = ["download", "rename", "delete"]\n+\n+ extensionsWithIcons = frozenset(\n+ [\n+ "aac",\n+ "avi",\n+ "bmp",\n+ "chm",\n+ "css",\n+ "dll",\n+ "doc",\n+ "fla",\n+ "gif",\n+ "htm",\n+ "html",\n+ "ini",\n+ "jar",\n+ "jpeg",\n+ "jpg",\n+ "js",\n+ "lasso",\n+ "mdb",\n+ "mov",\n+ "mp3",\n+ "mpg",\n+ "pdf",\n+ "php",\n+ "png",\n+ "ppt",\n+ "py",\n+ "rb",\n+ "real",\n+ "reg",\n+ "rtf",\n+ "sql",\n+ "swf",\n+ "txt",\n+ "vbs",\n+ "wav",\n+ "wma",\n+ "wmv",\n+ "xls",\n+ "xml",\n+ "xsl",\n+ "zip",\n+ ]\n )\n \n+ protectedActions = ("addfolder", "add", "addnew", "rename", "delete")\n+\n def pattern_options(self):\n site = getSite()\n- viewName = \'@@plone.resourceeditor.filemanager-actions\'\n- return json.dumps({\n- \'actionUrl\': \'{0}/++{1}++{2}/{3}\'.format(\n- site.absolute_url(),\n- self.context.__parent__.__parent__.__name__,\n- self.context.__name__,\n- viewName\n- )\n- })\n+ viewName = "@@plone.resourceeditor.filemanager-actions"\n+ return json.dumps(\n+ {\n+ "actionUrl": "{}/++{}++{}/{}".format(\n+ site.absolute_url(),\n+ self.context.__parent__.__parent__.__name__,\n+ self.context.__name__,\n+ viewName,\n+ )\n+ }\n+ )\n \n def mode_selector(self, form):\n # AJAX methods called by the file manager\n- mode = form[\'mode\']\n+ mode = form["mode"]\n \n if mode in self.protectedActions:\n authorize(self.context, self.request)\n \n- response = {\'error:\': \'Unknown request\', \'code\': -1}\n+ response = {"error:": "Unknown request", "code": -1}\n textareaWrap = False\n \n- if mode == u\'getfolder\':\n+ if mode == "getfolder":\n response = self.getFolder(\n- path=urllib.parse.unquote(form[\'path\']),\n- getSizes=form.get(\'getsizes\', \'false\') == \'true\'\n+ path=urllib.parse.unquote(form["path"]),\n+ getSizes=form.get("getsizes", "false") == "true",\n )\n- elif mode == u\'getinfo\':\n+ elif mode == "getinfo":\n response = self.getInfo(\n- path=urllib.parse.unquote(form[\'path\']),\n- getSize=form.get(\'getsize\', \'false\') == \'true\'\n+ path=urllib.parse.unquote(form["path"]),\n+ getSize=form.get("getsize", "false") == "true",\n )\n- elif mode == u\'addfolder\':\n+ elif mode == "addfolder":\n response = self.addFolder(\n- path=urllib.parse.unquote(form[\'path\']),\n- name=urllib.parse.unquote(form[\'name\'])\n+ path=urllib.parse.unquote(form["path"]),\n+ name=urllib.parse.unquote(form["name"]),\n )\n- elif mode == u\'add\':\n+ elif mode == "add":\n textareaWrap = True\n response = self.add(\n- path=urllib.parse.unquote(form[\'currentpath\']),\n- newfile=form[\'newfile\'],\n- replacepath=form.get(\'replacepath\', None)\n+ path=urllib.parse.unquote(form["currentpath"]),\n+ newfile=form["newfile"],\n+ replacepath=form.get("replacepath", None),\n )\n- elif mode == u\'addnew\':\n+ elif mode == "addnew":\n response = self.addNew(\n- path=urllib.parse.unquote(form[\'path\']),\n- name=urllib.parse.unquote(form[\'name\'])\n+ path=urllib.parse.unquote(form["path"]),\n+ name=urllib.parse.unquote(form["name"]),\n )\n- elif mode == u\'rename\':\n+ elif mode == "rename":\n response = self.rename(\n- path=urllib.parse.unquote(form[\'old\']),\n- newName=urllib.parse.unquote(form[\'new\'])\n- )\n- elif mode == u\'delete\':\n- response = self.delete(\n- path=urllib.parse.unquote(form[\'path\'])\n+ path=urllib.parse.unquote(form["old"]),\n+ newName=urllib.parse.unquote(form["new"]),\n )\n- elif mode == \'move\':\n+ elif mode == "delete":\n+ response = self.delete(path=urllib.parse.unquote(form["path"]))\n+ elif mode == "move":\n response = self.move(\n- path=urllib.parse.unquote(form[\'path\']),\n- directory=urllib.parse.unquote(form[\'directory\'])\n- )\n- elif mode == u\'download\':\n- return self.download(\n- path=urllib.parse.unquote(form[\'path\'])\n+ path=urllib.parse.unquote(form["path"]),\n+ directory=urllib.parse.unquote(form["directory"]),\n )\n+ elif mode == "download":\n+ return self.download(path=urllib.parse.unquote(form["path"]))\n if textareaWrap:\n- self.request.response.setHeader(\'Content-Type\', \'text/html\')\n- return \'\'.format(json.dumps(response))\n- self.request.response.setHeader(\'Content-Type\',\n- \'application/json\')\n+ self.request.response.setHeader("Content-Type", "text/html")\n+ return f""\n+ self.request.response.setHeader("Content-Type", "application/json")\n return json.dumps(response)\n \n def __call__(self):\n # make sure theme is disable for these requests\n- self.request.response.setHeader(\'X-Theme-Disabled\', \'True\')\n- self.request[\'HTTP_X_THEME_ENABLED\'] = False\n+ self.request.response.setHeader("X-Theme-Disabled", "True")\n+ self.request["HTTP_X_THEME_ENABLED"] = False\n \n self.setup()\n form = self.request.form\n \n # AJAX methods called by the file manager\n- if \'mode\' in form:\n+ if "mode" in form:\n return self.mode_selector(form)\n \n # Rendering the view\n@@ -700,7 +713,7 @@ def setup(self):\n \n @zproperty.Lazy\n def portalUrl(self):\n- return getToolByName(self.context, \'portal_url\')()\n+ return getToolByName(self.context, "portal_url")()\n \n @zproperty.Lazy\n def resourceDirectory(self):\n@@ -712,24 +725,22 @@ def resourceType(self):\n \n @zproperty.Lazy\n def baseUrl(self):\n- return \'{0}/++{1}++{2}\'.format(\n- self.portalUrl,\n- self.resourceType,\n- self.resourceDirectory.__name__\n+ return "{}/++{}++{}".format(\n+ self.portalUrl, self.resourceType, self.resourceDirectory.__name__\n )\n \n @zproperty.Lazy\n def fileConnector(self):\n- return \'{0}/@@{1}\'.format(self.baseUrl, self.__name__,)\n+ return f"{self.baseUrl}/@@{self.__name__}"\n \n @zproperty.Lazy\n def filemanagerConfiguration(self):\n return """\\\n var FILE_ROOT = \'/\';\n-var IMAGES_EXT = {0};\n-var CAPABILITIES = {1};\n-var FILE_CONNECTOR = \'{2}\';\n-var BASE_URL = \'{3}\';\n+var IMAGES_EXT = {};\n+var CAPABILITIES = {};\n+var FILE_CONNECTOR = \'{}\';\n+var BASE_URL = \'{}\';\n """.format(\n repr(self.imageExtensions),\n repr(self.capabilities),\n@@ -738,21 +749,21 @@ def filemanagerConfiguration(self):\n )\n \n def normalizePath(self, path):\n- if path.startswith(\'/\'):\n+ if path.startswith("/"):\n path = path[1:]\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n return path\n \n def normalizeReturnPath(self, path):\n- if path.endswith(\'/\'):\n+ if path.endswith("/"):\n path = path[:-1]\n- if not path.startswith(\'/\'):\n- path = \'/\' + path\n+ if not path.startswith("/"):\n+ path = "/" + path\n return path\n \n def parentPath(self, path):\n- return \'/\'.join(path.split(\'/\')[:-1])\n+ return "/".join(path.split("/")[:-1])\n \n # AJAX responses\n \n@@ -771,9 +782,6 @@ def getFolder(self, path, getSizes=False):\n to getFolder(). This can be used for example to only show image files\n in a file system tree.\n """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n-\n folders = []\n files = []\n \n@@ -782,11 +790,9 @@ def getFolder(self, path, getSizes=False):\n \n for name in folder.listDirectory():\n if IResourceDirectory.providedBy(folder[name]):\n- folders.append(self.getInfo(\n- path=\'{0}/{1}/\'.format(path, name), getSize=getSizes))\n+ folders.append(self.getInfo(path=f"{path}/{name}/", getSize=getSizes))\n else:\n- files.append(self.getInfo(\n- path=\'{0}/{1}\'.format(path, name), getSize=getSizes))\n+ files.append(self.getInfo(path=f"{path}/{name}", getSize=getSizes))\n return folders + files\n \n def getInfo(self, path, getSize=False):\n@@ -796,90 +802,75 @@ def getInfo(self, path, getSize=False):\n indicates whether the dimensions of the file (if an image) should be\n returned.\n """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n-\n path = self.normalizePath(path)\n obj = self.getObject(path)\n \n filename = obj.__name__\n- error = \'\'\n+ error = ""\n errorCode = 0\n \n properties = {\n- \'dateModified\': None,\n+ "dateModified": None,\n }\n \n if isinstance(obj, File):\n- properties[\'dateModified\'] = DateTime(obj._p_mtime).strftime(\'%c\')\n+ properties["dateModified"] = DateTime(obj._p_mtime).strftime("%c")\n size = obj.get_size() / 1024\n if size < 1024:\n- size_specifier = u\'kb\'\n+ size_specifier = "kb"\n else:\n- size_specifier = u\'mb\'\n+ size_specifier = "mb"\n size = size / 1024\n- properties[\'size\'] = \'{0}{1}\'.format(\n+ properties["size"] = "{}{}".format(\n size,\n- translate(_(u\'filemanager_{0}\'.format(size_specifier),\n- default=size_specifier), context=self.request)\n+ translate(\n+ _(f"filemanager_{size_specifier}", default=size_specifier),\n+ context=self.request,\n+ ),\n )\n \n- fileType = \'txt\'\n+ fileType = "txt"\n \n siteUrl = self.portalUrl\n resourceName = self.resourceDirectory.__name__\n \n- preview = \'{0}/{1}/images/fileicons/default.png\'.format(\n- siteUrl,\n- self.staticFiles\n- )\n+ preview = f"{siteUrl}/{self.staticFiles}/images/fileicons/default.png"\n \n if IResourceDirectory.providedBy(obj):\n- preview = \'{0}/{1}/images/fileicons/_Open.png\'.format(\n- siteUrl,\n- self.staticFiles\n+ preview = "{}/{}/images/fileicons/_Open.png".format(\n+ siteUrl, self.staticFiles\n )\n- fileType = \'dir\'\n- path = path + \'/\'\n+ fileType = "dir"\n+ path = path + "/"\n else:\n fileType = self.getExtension(path, obj)\n if fileType in self.imageExtensions:\n- preview = \'{0}/++{1}++{2}/{3}\'.format(\n- siteUrl,\n- self.resourceType,\n- resourceName,\n- path\n+ preview = "{}/++{}++{}/{}".format(\n+ siteUrl, self.resourceType, resourceName, path\n )\n elif fileType in self.extensionsWithIcons:\n- preview = \'{0}/{1}/images/fileicons/{2}.png\'.format(\n- siteUrl,\n- self.staticFiles,\n- fileType\n+ preview = "{}/{}/images/fileicons/{}.png".format(\n+ siteUrl, self.staticFiles, fileType\n )\n \n if isinstance(obj, Image):\n- properties[\'height\'] = obj.height\n- properties[\'width\'] = obj.width\n+ properties["height"] = obj.height\n+ properties["width"] = obj.width\n \n return {\n- \'path\': self.normalizeReturnPath(path),\n- \'filename\': filename,\n- \'fileType\': fileType,\n- \'preview\': preview,\n- \'properties\': properties,\n- \'error\': error,\n- \'code\': errorCode,\n+ "path": self.normalizeReturnPath(path),\n+ "filename": filename,\n+ "fileType": fileType,\n+ "preview": preview,\n+ "properties": properties,\n+ "error": error,\n+ "code": errorCode,\n }\n \n def addFolder(self, path, name):\n- """Create a new directory on the server within the given path.\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n-\n+ """Create a new directory on the server within the given path."""\n code = 0\n- error = \'\'\n+ error = ""\n \n parentPath = self.normalizePath(path)\n parent = None\n@@ -887,20 +878,26 @@ def addFolder(self, path, name):\n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_foldername", default="Invalid folder name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Folder already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Folder already exists.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n@@ -908,18 +905,18 @@ def addFolder(self, path, name):\n except UnicodeDecodeError:\n error = translate(\n _(\n- u\'filemanager_invalid_foldername\',\n- default=u\'Invalid folder name.\'\n+ "filemanager_invalid_foldername",\n+ default="Invalid folder name.",\n ),\n- context=self.request\n+ context=self.request,\n )\n code = 1\n \n return {\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n }\n \n def add(self, path, newfile, replacepath=None):\n@@ -932,225 +929,219 @@ def add(self, path, newfile, replacepath=None):\n uploaded file\'s name should be safe to use as a path component in a\n URL, so URL-encoded at a minimum.\n """\n-\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- if six.PY2 and replacepath is not None:\n- replacepath = safe_encode(replacepath, \'utf-8\')\n-\n parentPath = self.normalizePath(path)\n \n- error = \'\'\n+ error = ""\n code = 0\n \n name = newfile.filename\n- if six.PY2 and isinstance(name, six.text_type):\n- name = safe_encode(name, \'utf-8\')\n-\n if replacepath:\n newPath = replacepath\n- parentPath = \'/\'.join(replacepath.split(\'/\')[:-1])\n+ parentPath = "/".join(replacepath.split("/")[:-1])\n else:\n- newPath = \'{0}/{1}\'.format(parentPath, name,)\n+ newPath = f"{parentPath}/{name}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if name in parent and not replacepath:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n self.resourceDirectory.writeFile(newPath, newfile)\n except ValueError:\n- error = translate(_(u\'filemanager_error_file_invalid\',\n- default=u\'Could not read file.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_file_invalid",\n+ default="Could not read file.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n \n return {\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'path\': self.normalizeReturnPath(path),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "path": self.normalizeReturnPath(path),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n }\n \n def addNew(self, path, name):\n- """Add a new empty file in the given directory\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- name = safe_encode(name, \'utf-8\')\n-\n- error = \'\'\n+ """Add a new empty file in the given directory"""\n+ error = ""\n code = 0\n \n parentPath = self.normalizePath(path)\n- newPath = \'{0}/{1}\'.format(parentPath, name,)\n+ newPath = f"{parentPath}/{name}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if not validateFilename(name):\n- error = translate(_(u\'filemanager_invalid_filename\',\n- default=u\'Invalid file name.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_filename", default="Invalid file name."),\n+ context=self.request,\n+ )\n code = 1\n elif name in parent:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n- self.resourceDirectory.writeFile(newPath, b\'\')\n+ self.resourceDirectory.writeFile(newPath, b"")\n \n return {\n- \'parent\': self.normalizeReturnPath(parentPath),\n- \'name\': name,\n- \'error\': error,\n- \'code\': code,\n+ "parent": self.normalizeReturnPath(parentPath),\n+ "name": name,\n+ "error": error,\n+ "code": code,\n }\n \n def rename(self, path, newName):\n- """Rename the item at the given path to the new name\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- newName = safe_encode(newName, \'utf-8\')\n-\n+ """Rename the item at the given path to the new name"""\n npath = self.normalizePath(path)\n- oldPath = newPath = \'/\'.join(npath.split(\'/\')[:-1])\n- oldName = npath.split(\'/\')[-1]\n+ oldPath = newPath = "/".join(npath.split("/")[:-1])\n+ oldName = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(oldPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n if newName != oldName:\n if newName in parent:\n error = translate(\n- _(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n- context=self.request)\n+ _(\n+ "filemanager_error_file_exists",\n+ default="File already exists.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n else:\n parent.rename(oldName, newName)\n \n return {\n- \'oldParent\': self.normalizeReturnPath(oldPath),\n- \'oldName\': oldName,\n- \'newParent\': self.normalizeReturnPath(newPath),\n- \'newName\': newName,\n- \'error\': error,\n- \'code\': code,\n+ "oldParent": self.normalizeReturnPath(oldPath),\n+ "oldName": oldName,\n+ "newParent": self.normalizeReturnPath(newPath),\n+ "newName": newName,\n+ "error": error,\n+ "code": code,\n }\n \n def delete(self, path):\n- """Delete the item at the given path.\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n-\n+ """Delete the item at the given path."""\n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n code = 0\n- error = \'\'\n+ error = ""\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n else:\n try:\n del parent[name]\n except KeyError:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n \n return {\n- \'path\': self.normalizeReturnPath(path),\n- \'error\': error,\n- \'code\': code,\n+ "path": self.normalizeReturnPath(path),\n+ "error": error,\n+ "code": code,\n }\n \n def move(self, path, directory):\n- """Move the item at the given path to a new directory\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- directory = safe_encode(directory, \'utf-8\')\n-\n+ """Move the item at the given path to a new directory"""\n npath = self.normalizePath(path)\n newParentPath = self.normalizePath(directory)\n \n parentPath = self.parentPath(npath)\n- filename = npath.split(\'/\')[-1]\n+ filename = npath.split("/")[-1]\n \n code = 0\n- error = \'\'\n- newCanonicalPath = \'{0}/{1}\'.format(newParentPath, filename)\n+ error = ""\n+ newCanonicalPath = f"{newParentPath}/{filename}"\n \n try:\n parent = self.getObject(parentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_invalid_parent\',\n- default=u\'Parent folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_invalid_parent", default="Parent folder not found."),\n+ context=self.request,\n+ )\n code = 1\n return {\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n }\n \n try:\n target = self.getObject(newParentPath)\n except KeyError:\n- error = translate(_(u\'filemanager_error_folder_exists\',\n- default=u\'Destination folder not found.\'),\n- context=self.request)\n+ error = translate(\n+ _(\n+ "filemanager_error_folder_exists",\n+ default="Destination folder not found.",\n+ ),\n+ context=self.request,\n+ )\n code = 1\n return {\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n }\n \n if filename not in parent:\n- error = translate(_(u\'filemanager_error_file_not_found\',\n- default=u\'File not found.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_not_found", default="File not found."),\n+ context=self.request,\n+ )\n code = 1\n elif filename in target:\n- error = translate(_(u\'filemanager_error_file_exists\',\n- default=u\'File already exists.\'),\n- context=self.request)\n+ error = translate(\n+ _("filemanager_error_file_exists", default="File already exists."),\n+ context=self.request,\n+ )\n code = 1\n else:\n obj = parent[filename]\n@@ -1158,28 +1149,22 @@ def move(self, path, directory):\n target[filename] = obj\n \n return {\n- \'code\': code,\n- \'error\': error,\n- \'newPath\': self.normalizeReturnPath(newCanonicalPath),\n+ "code": code,\n+ "error": error,\n+ "newPath": self.normalizeReturnPath(newCanonicalPath),\n }\n \n def download(self, path):\n- """Serve the requested file to the user\n- """\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n-\n+ """Serve the requested file to the user"""\n npath = self.normalizePath(path)\n- parentPath = \'/\'.join(npath.split(\'/\')[:-1])\n- name = npath.split(\'/\')[-1]\n+ parentPath = "/".join(npath.split("/")[:-1])\n+ name = npath.split("/")[-1]\n \n parent = self.getObject(parentPath)\n \n- self.request.response.setHeader(\'Content-Type\',\n- \'application/octet-stream\')\n+ self.request.response.setHeader("Content-Type", "application/octet-stream")\n self.request.response.setHeader(\n- \'Content-Disposition\',\n- \'attachment; filename="{0}"\'.format(name)\n+ "Content-Disposition", f\'attachment; filename="{name}"\'\n )\n \n # TODO: Use streams here if we can\n@@ -1192,7 +1177,10 @@ def getObject(self, path):\n return self.resourceDirectory\n try:\n return self.resourceDirectory[path]\n- except (KeyError, NotFound,):\n+ except (\n+ KeyError,\n+ NotFound,\n+ ):\n raise KeyError(path)\n \n def getExtension(self, path, obj):\n@@ -1202,8 +1190,8 @@ def getExtension(self, path, obj):\n ct = obj.getContentType()\n if ct:\n # take content type of the file over extension if available\n- if \'/\' in ct:\n- _ext = ct.split(\'/\')[1].lower()\n+ if "/" in ct:\n+ _ext = ct.split("/")[1].lower()\n if _ext in self.extensionsWithIcons:\n return _ext\n return ext\n@@ -1211,57 +1199,51 @@ def getExtension(self, path, obj):\n # Methods that are their own views\n def getFile(self, path):\n self.setup()\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n-\n path = self.normalizePath(path)\n file = self.context.context.unrestrictedTraverse(path)\n ext = self.getExtension(path, file)\n- result = {\'ext\': ext}\n+ result = {"ext": ext}\n if ext not in self.imageExtensions:\n- result[\'contents\'] = str(file.data)\n+ result["contents"] = str(file.data)\n else:\n info = self.getInfo(path)\n- result[\'info\'] = self.previewTemplate(info=info)\n+ result["info"] = self.previewTemplate(info=info)\n \n- self.request.response.setHeader(\'Content-Type\', \'application/json\')\n+ self.request.response.setHeader("Content-Type", "application/json")\n return json.dumps(result)\n \n def saveFile(self, path, value):\n- path = self.request.form.get(\'path\', path)\n- value = self.request.form.get(\'value\', value)\n- if six.PY2:\n- path = safe_encode(path, \'utf-8\')\n- value = safe_encode(value, \'utf-8\')\n- path = path.lstrip(\'/\')\n- value = value.replace(\'\\r\\n\', \'\\n\')\n+ path = self.request.form.get("path", path)\n+ value = self.request.form.get("value", value)\n+ path = path.lstrip("/")\n+ value = value.replace("\\r\\n", "\\n")\n self.context.writeFile(path, value)\n- return \' \' # Zope no likey empty responses\n+ return " " # Zope does not like empty responses\n \n def filetree(self):\n+ foldersOnly = bool(self.request.get("foldersOnly", False))\n \n- foldersOnly = bool(self.request.get(\'foldersOnly\', False))\n-\n- def getFolder(root, relpath=\'\'):\n+ def getFolder(root, relpath=""):\n result = []\n for name in root.listDirectory():\n- path = \'{0}/{1}\'.format(relpath, name)\n+ path = f"{relpath}/{name}"\n if IResourceDirectory.providedBy(root[name]):\n- item = {\n- \'title\': name,\n- \'key\': path,\n- \'isFolder\': True\n- }\n- item[\'children\'] = getFolder(root[name], path)\n+ item = {"title": name, "key": path, "isFolder": True}\n+ item["children"] = getFolder(root[name], path)\n result.append(item)\n elif not foldersOnly:\n- item = {\'title\': name, \'key\': path}\n+ item = {"title": name, "key": path}\n result.append(item)\n return result\n- return json.dumps([{\n- \'title\': \'/\',\n- \'key\': \'/\',\n- \'isFolder\': True,\n- \'expand\': True,\n- \'children\': getFolder(self.context)\n- }])\n+\n+ return json.dumps(\n+ [\n+ {\n+ "title": "/",\n+ "key": "/",\n+ "isFolder": True,\n+ "expand": True,\n+ "children": getFolder(self.context),\n+ }\n+ ]\n+ )\ndiff --git a/plone/resourceeditor/configure.zcml b/plone/resourceeditor/configure.zcml\nindex 3ea8f8d..84aaf76 100644\n--- a/plone/resourceeditor/configure.zcml\n+++ b/plone/resourceeditor/configure.zcml\n@@ -2,52 +2,53 @@\n xmlns="http://namespaces.zope.org/zope"\n xmlns:browser="http://namespaces.zope.org/browser"\n xmlns:zcml="http://namespaces.zope.org/zcml"\n- i18n_domain="plone">\n-\n- \n-\n- \n-\n- \n-\n- \n-\n- \n-\n- \n-\n- \n+ i18n_domain="plone"\n+ >\n+\n+ \n+\n+ \n+\n+ \n+\n+ \n+\n+ \n+\n+ \n+\n+ \n \n \ndiff --git a/plone/resourceeditor/editor.pt b/plone/resourceeditor/editor.pt\nindex 9ea1049..f7cfe66 100644\n--- a/plone/resourceeditor/editor.pt\n+++ b/plone/resourceeditor/editor.pt\n@@ -1,28 +1,45 @@\n-
\n+
\n \n- \n+ \n \n- \n- \n- \n+ \n+ \n+ \n \n- \n- \n- \n+ \n+ \n \n+ \n \n- \n+ \n \n- \n-
\n-
\n+ \n+
\n+
\n \n-
\n+
\n
\ndiff --git a/plone/resourceeditor/preview.pt b/plone/resourceeditor/preview.pt\nindex 60f5a0c..e522369 100644\n--- a/plone/resourceeditor/preview.pt\n+++ b/plone/resourceeditor/preview.pt\n@@ -1,16 +1,30 @@\n \n-
\n- \n-

\n- Date Modified: \n- n/a\n-
\n- Size: \n- n/a\n-

\n+ xmlns:metal="http://xml.zope.org/namespaces/metal"\n+ xmlns:tal="http://xml.zope.org/namespaces/tal"\n+ class="info"\n+ xml:lang="en"\n+ tal:define="\n+ info options/info;\n+ props info/properties;\n+ "\n+>\n+ \n+

\n+ Date Modified:\n+ \n+ n/a\n+
\n+ Size:\n+ \n+ n/a\n+

\n
\ndiff --git a/plone/resourceeditor/testing.py b/plone/resourceeditor/testing.py\nindex de5cbe1..e4ef105 100644\n--- a/plone/resourceeditor/testing.py\n+++ b/plone/resourceeditor/testing.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.app.testing import applyProfile\n from plone.app.testing import IntegrationTesting\n from plone.app.testing import PLONE_FIXTURE\n@@ -11,19 +10,18 @@ class PloneResourceEditor(PloneSandboxLayer):\n def setUpZope(self, app, configurationContext):\n # Load ZCML\n import plone.resourceeditor\n+\n self.loadZCML(\n- \'configure.zcml\',\n- package=plone.resourceeditor,\n- context=configurationContext\n+ "configure.zcml", package=plone.resourceeditor, context=configurationContext\n )\n \n def setUpPloneSite(self, portal):\n # install plone.resource\n- applyProfile(portal, \'plone.resource:default\')\n+ applyProfile(portal, "plone.resource:default")\n \n \n PLONE_RESOURCE_EDITOR_FIXTURE = PloneResourceEditor()\n PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING = IntegrationTesting(\n- bases=(PLONE_RESOURCE_EDITOR_FIXTURE, ),\n- name=\'plone.resourceeditor:Integration\',\n+ bases=(PLONE_RESOURCE_EDITOR_FIXTURE,),\n+ name="plone.resourceeditor:Integration",\n )\ndiff --git a/plone/resourceeditor/tests/test_file_manager.py b/plone/resourceeditor/tests/test_file_manager.py\nindex c3312e5..01f79c6 100644\n--- a/plone/resourceeditor/tests/test_file_manager.py\n+++ b/plone/resourceeditor/tests/test_file_manager.py\n@@ -1,19 +1,16 @@\n-# -*- coding: utf-8 -*-\n-from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING # noqa\n+from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n \n-import six\n import unittest\n \n \n class TestResourceEditorOperations(unittest.TestCase):\n-\n layer = PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n \n- def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n+ def _make_directory(self, resourcetype="theme", resourcename="mytheme"):\n from plone.resource.interfaces import IResourceDirectory\n from zope.component import getUtility\n \n- resources = getUtility(IResourceDirectory, name=\'persistent\')\n+ resources = getUtility(IResourceDirectory, name="persistent")\n resources.makeDirectory(resourcetype)\n resources[resourcetype].makeDirectory(resourcename)\n \n@@ -21,375 +18,398 @@ def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n \n def test_getinfo(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- r.writeFile(\'test.txt\', b\'A text file\')\n+ r.writeFile("test.txt", b"A text file")\n \n- view = FileManager(r, self.layer[\'request\'])\n- info = view.getInfo(\'/test.txt\')\n+ view = FileManager(r, self.layer["request"])\n+ info = view.getInfo("/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'fileType\'], \'txt\')\n- self.assertEqual(info[\'filename\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["fileType"], "txt")\n+ self.assertEqual(info["filename"], "test.txt")\n+ self.assertEqual(info["path"], "/test.txt")\n \n def test_getfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'beta.txt\', b\'Beta\')\n- r[\'alpha\'].makeDirectory(\'delta\')\n- r[\'alpha\'][\'delta\'].writeFile(\'gamma.css\', b\'body\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("beta.txt", b"Beta")\n+ r["alpha"].makeDirectory("delta")\n+ r["alpha"]["delta"].writeFile("gamma.css", b"body")\n \n- view = FileManager(r, self.layer[\'request\'])\n- info = view.getFolder(\'/alpha\')\n+ view = FileManager(r, self.layer["request"])\n+ info = view.getFolder("/alpha")\n \n self.assertEqual(len(info), 2)\n \n- self.assertEqual(info[0][\'code\'], 0)\n- self.assertEqual(info[0][\'error\'], \'\')\n- self.assertEqual(info[0][\'fileType\'], \'dir\')\n- self.assertEqual(info[0][\'filename\'], \'delta\')\n- self.assertEqual(info[0][\'path\'], \'/alpha/delta\')\n+ self.assertEqual(info[0]["code"], 0)\n+ self.assertEqual(info[0]["error"], "")\n+ self.assertEqual(info[0]["fileType"], "dir")\n+ self.assertEqual(info[0]["filename"], "delta")\n+ self.assertEqual(info[0]["path"], "/alpha/delta")\n \n- self.assertEqual(info[1][\'code\'], 0)\n- self.assertEqual(info[1][\'error\'], \'\')\n- self.assertEqual(info[1][\'fileType\'], \'txt\')\n- self.assertEqual(info[1][\'filename\'], \'beta.txt\')\n- self.assertEqual(info[1][\'path\'], \'/alpha/beta.txt\')\n+ self.assertEqual(info[1]["code"], 0)\n+ self.assertEqual(info[1]["error"], "")\n+ self.assertEqual(info[1]["fileType"], "txt")\n+ self.assertEqual(info[1]["filename"], "beta.txt")\n+ self.assertEqual(info[1]["path"], "/alpha/beta.txt")\n \n def test_addfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addFolder(\'/\', \'alpha\')\n+ info = view.addFolder("/", "alpha")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n- info = view.addFolder(\'/alpha\', \'beta\')\n+ info = view.addFolder("/alpha", "beta")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_addfolder_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addFolder(\'/\', \'alpha\')\n+ info = view.addFolder("/", "alpha")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n def test_addfolder_invalid_name(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addFolder(\'/\', \'foo\' + char)\n+ info = view.addFolder("/", "foo" + char)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'foo\' + char)\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "foo" + char)\n \n def test_addfolder_invalid_parent(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addFolder(\'/alpha\', \'beta\')\n+ info = view.addFolder("/alpha", "beta")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_add(self):\n+ from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n- info = view.add(\'/\', d)\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n+ info = view.add("/", d)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n+ self.assertEqual(info["parent"], "/")\n \n def test_add_subfolder(self):\n+ from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n \n- info = view.add(\'/alpha\', d)\n+ info = view.add("/alpha", d)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/alpha\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/alpha")\n+ self.assertEqual(info["parent"], "/alpha")\n \n def test_add_exists(self):\n+ from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'boo\')\n+ r.writeFile("test.txt", b"boo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n \n- info = view.add(\'/\', d)\n+ info = view.add("/", d)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'boo\')\n+ self.assertEqual(r.readFile("test.txt"), b"boo")\n \n def test_add_replace(self):\n+ from io import BytesIO\n from plone.resourceeditor.browser import FileManager\n- from six import BytesIO\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'boo\')\n+ r.writeFile("test.txt", b"boo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- d = BytesIO(b\'foo\')\n- d.filename = \'test.txt\'\n+ d = BytesIO(b"foo")\n+ d.filename = "test.txt"\n \n- info = view.add(\'/\', d, \'/test.txt\')\n+ info = view.add("/", d, "/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n+ self.assertEqual(info["parent"], "/")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("test.txt"), b"foo")\n \n def test_addnew(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addNew(\'/\', \'test.txt\')\n+ info = view.addNew("/", "test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["parent"], "/")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'\')\n+ self.assertEqual(r.readFile("test.txt"), b"")\n \n def test_addnew_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.addNew(\'/\', \'test.txt\')\n+ info = view.addNew("/", "test.txt")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("test.txt"), b"foo")\n \n def test_addnew_invalidname(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addNew(\'/\', \'foo\' + char)\n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ info = view.addNew("/", "foo" + char)\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n def test_rename(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.rename(\'/test.txt\', \'foo.txt\')\n+ info = view.rename("/test.txt", "foo.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("foo.txt"), b"foo")\n \n def test_rename_subfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.rename(\'/alpha/test.txt\', \'foo.txt\')\n+ info = view.rename("/alpha/test.txt", "foo.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/alpha\')\n- self.assertEqual(info[\'newParent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/alpha")\n+ self.assertEqual(info["newParent"], "/alpha")\n \n- self.assertEqual(r[\'alpha\'].readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r["alpha"].readFile("foo.txt"), b"foo")\n \n def test_rename_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n- r.writeFile(\'foo.txt\', b\'bar\')\n+ r.writeFile("test.txt", b"foo")\n+ r.writeFile("foo.txt", b"bar")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.rename(\'/test.txt\', \'foo.txt\')\n+ info = view.rename("/test.txt", "foo.txt")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'bar\')\n+ self.assertEqual(r.readFile("foo.txt"), b"bar")\n \n def test_delete(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n+ self.assertFalse("test.txt" in r)\n \n def test_delete_subfolder(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.delete(\'/alpha/test.txt\')\n+ info = view.delete("/alpha/test.txt")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r[\'alpha\'])\n+ self.assertFalse("test.txt" in r["alpha"])\n \n def test_delete_notfound(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n def test_move(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n- self.assertEqual(b\'foo\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertFalse("test.txt" in r)\n+ self.assertEqual(b"foo", r["alpha"].readFile("test.txt"))\n \n def test_move_exists(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'bar\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"bar")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n- self.assertEqual(b\'bar\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertTrue("test.txt" in r)\n+ self.assertEqual(b"bar", r["alpha"].readFile("test.txt"))\n \n def test_move_invalid_parent(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n+ view = FileManager(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n+ self.assertTrue("test.txt" in r)\n \n def test_download(self):\n from plone.resourceeditor.browser import FileManager\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManager(r, self.layer[\'request\'])\n- self.assertEqual(b\'foo\', view.download(\'/test.txt\'))\n+ view = FileManager(r, self.layer["request"])\n+ self.assertEqual(b"foo", view.download("/test.txt"))\ndiff --git a/plone/resourceeditor/tests/test_file_manager_action.py b/plone/resourceeditor/tests/test_file_manager_action.py\nindex b724976..46d1b94 100644\n--- a/plone/resourceeditor/tests/test_file_manager_action.py\n+++ b/plone/resourceeditor/tests/test_file_manager_action.py\n@@ -1,18 +1,17 @@\n-# -*- coding: utf-8 -*-\n-from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING # noqa\n+from plone.resourceeditor.testing import PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n+\n import json\n import unittest\n \n \n class TestResourceEditorOperations(unittest.TestCase):\n-\n layer = PLONE_RESOURCE_EDITOR_INTEGRATION_TESTING\n \n- def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n+ def _make_directory(self, resourcetype="theme", resourcename="mytheme"):\n from plone.resource.interfaces import IResourceDirectory\n from zope.component import getUtility\n \n- resources = getUtility(IResourceDirectory, name=\'persistent\')\n+ resources = getUtility(IResourceDirectory, name="persistent")\n resources.makeDirectory(resourcetype)\n resources[resourcetype].makeDirectory(resourcename)\n \n@@ -20,330 +19,350 @@ def _make_directory(self, resourcetype=\'theme\', resourcename=\'mytheme\'):\n \n def test_getinfo(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- r.writeFile(\'test.txt\', b\'A text file\')\n- view = FileManagerActions(r, self.layer[\'request\'])\n- info = view.getInfo(r[\'test.txt\'])\n+ r.writeFile("test.txt", b"A text file")\n+ view = FileManagerActions(r, self.layer["request"])\n+ info = view.getInfo(r["test.txt"])\n \n- self.assertEqual(info[\'fileType\'], \'txt\')\n- self.assertEqual(info[\'filename\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n+ self.assertEqual(info["fileType"], "txt")\n+ self.assertEqual(info["filename"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n \n def test_getfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'beta.txt\', b\'Beta\')\n- r[\'alpha\'].makeDirectory(\'delta\')\n- r[\'alpha\'][\'delta\'].writeFile(\'gamma.css\', b\'body\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("beta.txt", b"Beta")\n+ r["alpha"].makeDirectory("delta")\n+ r["alpha"]["delta"].writeFile("gamma.css", b"body")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n- info = view.getFolder(\'/alpha\')\n+ view = FileManagerActions(r, self.layer["request"])\n+ info = view.getFolder("/alpha")\n \n self.assertEqual(len(info), 2)\n- self.assertEqual(info[0][\'fileType\'], \'dir\')\n- self.assertEqual(info[0][\'filename\'], \'delta\')\n- self.assertEqual(info[0][\'path\'], \'/alpha/delta/\')\n+ self.assertEqual(info[0]["fileType"], "dir")\n+ self.assertEqual(info[0]["filename"], "delta")\n+ self.assertEqual(info[0]["path"], "/alpha/delta/")\n \n- self.assertEqual(info[1][\'fileType\'], \'txt\')\n- self.assertEqual(info[1][\'filename\'], \'beta.txt\')\n- self.assertEqual(info[1][\'path\'], \'/alpha/beta.txt\')\n+ self.assertEqual(info[1]["fileType"], "txt")\n+ self.assertEqual(info[1]["filename"], "beta.txt")\n+ self.assertEqual(info[1]["path"], "/alpha/beta.txt")\n \n def test_addfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info_str = view.addFolder(\'/\', \'alpha\')\n+ info_str = view.addFolder("/", "alpha")\n info = json.loads(info_str)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n- info_str = view.addFolder(\'/alpha\', \'beta\')\n+ info_str = view.addFolder("/alpha", "beta")\n info = json.loads(info_str)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_addfolder_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info_str = view.addFolder(\'/\', \'alpha\')\n+ info_str = view.addFolder("/", "alpha")\n info = json.loads(info_str)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'alpha\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "alpha")\n \n def test_addfolder_invalid_name(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addFolder(\'/\', \'foo\' + char)\n+ info = view.addFolder("/", "foo" + char)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/\')\n- self.assertEqual(info[\'name\'], \'foo\' + char)\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/")\n+ self.assertEqual(info["name"], "foo" + char)\n \n def test_addfolder_invalid_parent(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.addFolder(\'/alpha\', \'beta\')\n+ info = view.addFolder("/alpha", "beta")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n- self.assertEqual(info[\'name\'], \'beta\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["parent"], "/alpha")\n+ self.assertEqual(info["name"], "beta")\n \n def test_add(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- d = \'test.txt\'\n+ d = "test.txt"\n \n- info = view.addFile(\'/\', d)\n+ info = view.addFile("/", d)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/\')\n- self.assertEqual(info[\'parent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/")\n+ self.assertEqual(info["parent"], "/")\n \n def test_add_subfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n+ r.makeDirectory("alpha")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- d = \'test.txt\'\n+ d = "test.txt"\n \n- info = view.addFile(\'/alpha\', d)\n+ info = view.addFile("/alpha", d)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'name\'], \'test.txt\')\n- self.assertEqual(info[\'path\'], \'/alpha\')\n- self.assertEqual(info[\'parent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["name"], "test.txt")\n+ self.assertEqual(info["path"], "/alpha")\n+ self.assertEqual(info["parent"], "/alpha")\n \n def test_add_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'boo\')\n+ r.writeFile("test.txt", b"boo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- d = \'test.txt\'\n+ d = "test.txt"\n \n- info = view.addFile(\'/\', d)\n+ info = view.addFile("/", d)\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n- self.assertEqual(r.readFile(\'test.txt\'), b\'boo\')\n+ self.assertEqual(r.readFile("test.txt"), b"boo")\n \n def test_addnew_invalidname(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n for char in \'\\\\/:*?"<>\':\n- info = view.addFile(\'/\', \'foo\' + char)\n+ info = view.addFile("/", "foo" + char)\n info = json.loads(info)\n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n \n def test_rename(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.renameFile(\'/test.txt\', \'foo.txt\')\n+ info = view.renameFile("/test.txt", "foo.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r.readFile("foo.txt"), b"foo")\n \n def test_rename_subfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.renameFile(\'/alpha/test.txt\', \'foo.txt\')\n+ info = view.renameFile("/alpha/test.txt", "foo.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/alpha\')\n- self.assertEqual(info[\'newParent\'], \'/alpha\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/alpha")\n+ self.assertEqual(info["newParent"], "/alpha")\n \n- self.assertEqual(r[\'alpha\'].readFile(\'foo.txt\'), b\'foo\')\n+ self.assertEqual(r["alpha"].readFile("foo.txt"), b"foo")\n \n def test_rename_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n- r.writeFile(\'foo.txt\', b\'bar\')\n+ r.writeFile("test.txt", b"foo")\n+ r.writeFile("foo.txt", b"bar")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.renameFile(\'/test.txt\', \'foo.txt\')\n+ info = view.renameFile("/test.txt", "foo.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'oldName\'], \'test.txt\')\n- self.assertEqual(info[\'newName\'], \'foo.txt\')\n- self.assertEqual(info[\'oldParent\'], \'/\')\n- self.assertEqual(info[\'newParent\'], \'/\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["oldName"], "test.txt")\n+ self.assertEqual(info["newName"], "foo.txt")\n+ self.assertEqual(info["oldParent"], "/")\n+ self.assertEqual(info["newParent"], "/")\n \n- self.assertEqual(r.readFile(\'foo.txt\'), b\'bar\')\n+ self.assertEqual(r.readFile("foo.txt"), b"bar")\n \n def test_delete(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n+ self.assertFalse("test.txt" in r)\n \n def test_delete_subfolder(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.delete(\'/alpha/test.txt\')\n+ info = view.delete("/alpha/test.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r[\'alpha\'])\n+ self.assertFalse("test.txt" in r["alpha"])\n \n def test_delete_notfound(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.delete(\'/test.txt\')\n+ info = view.delete("/test.txt")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'path\'], \'/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["path"], "/test.txt")\n \n def test_move(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 0)\n- self.assertEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 0)\n+ self.assertEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertFalse(\'test.txt\' in r)\n- self.assertEqual(b\'foo\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertFalse("test.txt" in r)\n+ self.assertEqual(b"foo", r["alpha"].readFile("test.txt"))\n \n def test_move_exists(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.makeDirectory(\'alpha\')\n- r[\'alpha\'].writeFile(\'test.txt\', b\'bar\')\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.makeDirectory("alpha")\n+ r["alpha"].writeFile("test.txt", b"bar")\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n- self.assertEqual(b\'bar\', r[\'alpha\'].readFile(\'test.txt\'))\n+ self.assertTrue("test.txt" in r)\n+ self.assertEqual(b"bar", r["alpha"].readFile("test.txt"))\n \n def test_move_invalid_parent(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n+ view = FileManagerActions(r, self.layer["request"])\n \n- info = view.move(\'/test.txt\', \'/alpha\')\n+ info = view.move("/test.txt", "/alpha")\n info = json.loads(info)\n \n- self.assertEqual(info[\'code\'], 1)\n- self.assertNotEqual(info[\'error\'], \'\')\n- self.assertEqual(info[\'newPath\'], \'/alpha/test.txt\')\n+ self.assertEqual(info["code"], 1)\n+ self.assertNotEqual(info["error"], "")\n+ self.assertEqual(info["newPath"], "/alpha/test.txt")\n \n- self.assertTrue(\'test.txt\' in r)\n+ self.assertTrue("test.txt" in r)\n \n def test_download(self):\n from plone.resourceeditor.browser import FileManagerActions\n+\n r = self._make_directory()\n- r.writeFile(\'test.txt\', b\'foo\')\n+ r.writeFile("test.txt", b"foo")\n \n- view = FileManagerActions(r, self.layer[\'request\'])\n- self.assertEqual(b\'foo\', view.download(\'/test.txt\'))\n+ view = FileManagerActions(r, self.layer["request"])\n+ self.assertEqual(b"foo", view.download("/test.txt"))\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..860fd37 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -1,3 +1,5 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n [tool.towncrier]\n filename = "CHANGES.rst"\n directory = "news/"\n@@ -18,3 +20,67 @@ showcontent = true\n directory = "bugfix"\n name = "Bug fixes:"\n showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "internal"\n+name = "Internal:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "documentation"\n+name = "Documentation:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "tests"\n+name = "Tests"\n+showcontent = true\n+\n+[tool.isort]\n+profile = "plone"\n+\n+[tool.black]\n+target-version = ["py38"]\n+\n+[tool.codespell]\n+ignore-words-list = "discreet"\n+\n+[tool.dependencychecker]\n+Zope = [\n+ # Zope own provided namespaces\n+ \'App\', \'OFS\', \'Products.Five\', \'Products.OFSP\', \'Products.PageTemplates\',\n+ \'Products.SiteAccess\', \'Shared\', \'Testing\', \'ZPublisher\', \'ZTUtils\',\n+ \'Zope2\', \'webdav\', \'zmi\',\n+ # ExtensionClass own provided namespaces\n+ \'ExtensionClass\', \'ComputedAttribute\', \'MethodObject\',\n+ # Zope dependencies\n+ \'AccessControl\', \'Acquisition\', \'AuthEncoding\', \'beautifulsoup4\', \'BTrees\',\n+ \'cffi\', \'Chameleon\', \'DateTime\', \'DocumentTemplate\',\n+ \'MultiMapping\', \'multipart\', \'PasteDeploy\', \'Persistence\', \'persistent\',\n+ \'pycparser\', \'python-gettext\', \'pytz\', \'RestrictedPython\', \'roman\',\n+ \'soupsieve\', \'transaction\', \'waitress\', \'WebOb\', \'WebTest\', \'WSGIProxy2\',\n+ \'z3c.pt\', \'zc.lockfile\', \'ZConfig\', \'zExceptions\', \'ZODB\', \'zodbpickle\',\n+ \'zope.annotation\', \'zope.browser\', \'zope.browsermenu\', \'zope.browserpage\',\n+ \'zope.browserresource\', \'zope.cachedescriptors\', \'zope.component\',\n+ \'zope.configuration\', \'zope.container\', \'zope.contentprovider\',\n+ \'zope.contenttype\', \'zope.datetime\', \'zope.deferredimport\',\n+ \'zope.deprecation\', \'zope.dottedname\', \'zope.event\', \'zope.exceptions\',\n+ \'zope.filerepresentation\', \'zope.globalrequest\', \'zope.hookable\',\n+ \'zope.i18n\', \'zope.i18nmessageid\', \'zope.interface\', \'zope.lifecycleevent\',\n+ \'zope.location\', \'zope.pagetemplate\', \'zope.processlifetime\', \'zope.proxy\',\n+ \'zope.ptresource\', \'zope.publisher\', \'zope.schema\', \'zope.security\',\n+ \'zope.sequencesort\', \'zope.site\', \'zope.size\', \'zope.structuredtext\',\n+ \'zope.tal\', \'zope.tales\', \'zope.testbrowser\', \'zope.testing\',\n+ \'zope.traversing\', \'zope.viewlet\'\n+]\n+\'Products.CMFCore\' = [\n+ \'docutils\', \'five.localsitemanager\', \'Missing\', \'Products.BTreeFolder2\',\n+ \'Products.GenericSetup\', \'Products.MailHost\', \'Products.PythonScripts\',\n+ \'Products.StandardCacheManagers\', \'Products.ZCatalog\', \'Record\',\n+ \'zope.sendmail\', \'Zope\'\n+]\n+\'plone.base\' = [\n+ \'plone.batching\', \'plone.registry\', \'plone.schema\',\'plone.z3cform\',\n+ \'Products.CMFCore\', \'Products.CMFDynamicViewFTI\',\n+]\n+python-dateutil = [\'dateutil\']\ndiff --git a/setup.cfg b/setup.cfg\nindex 2a9acf1..0da8f8f 100644\n--- a/setup.cfg\n+++ b/setup.cfg\n@@ -1,2 +1,23 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n [bdist_wheel]\n-universal = 1\n+universal = 0\n+\n+[flake8]\n+doctests = 1\n+ignore =\n+ # black takes care of line length\n+ E501,\n+ # black takes care of where to break lines\n+ W503,\n+ # black takes care of spaces within slicing (list[:])\n+ E203,\n+ # black takes care of spaces after commas\n+ E231,\n+\n+[check-manifest]\n+ignore =\n+ .editorconfig\n+ .meta.toml\n+ .pre-commit-config.yaml\n+ tox.ini\ndiff --git a/setup.py b/setup.py\nindex bebd322..50da757 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -1,53 +1,45 @@\n-from setuptools import setup, find_packages\n+from setuptools import find_packages\n+from setuptools import setup\n \n-version = \'3.0.5.dev0\'\n+\n+version = "4.0.0.dev0"\n \n setup(\n- name=\'plone.resourceeditor\',\n+ name="plone.resourceeditor",\n version=version,\n description="Integrates ACE editor into Plone",\n- long_description=(\n- open("README.rst").read() +\n- "\\n" +\n- open("CHANGES.rst").read()\n- ),\n+ long_description=(open("README.rst").read() + "\\n" + open("CHANGES.rst").read()),\n classifiers=[\n "Development Status :: 5 - Production/Stable",\n "Framework :: Plone",\n- "Framework :: Plone :: 5.2",\n "Framework :: Plone :: 6.0",\n "Framework :: Plone :: Core",\n "License :: OSI Approved :: GNU General Public License (GPL)",\n "Programming Language :: Python",\n- "Programming Language :: Python :: 2.7",\n- "Programming Language :: Python :: 3.6",\n- "Programming Language :: Python :: 3.7",\n "Programming Language :: Python :: 3.8",\n "Programming Language :: Python :: 3.9",\n "Programming Language :: Python :: 3.10",\n- ],\n- keywords=\'ace resource editor\',\n- author=\'Plone Foundation\',\n- author_email=\'plone-developers@lists.sourceforge.net\',\n- url=\'https://github.com/plone/plone.resourceeditor\',\n- license=\'GPL\',\n+ "Programming Language :: Python :: 3.11",\n+ ],\n+ keywords="ace resource editor",\n+ author="Plone Foundation",\n+ author_email="plone-developers@lists.sourceforge.net",\n+ url="https://github.com/plone/plone.resourceeditor",\n+ license="GPL",\n packages=find_packages(),\n- namespace_packages=[\'plone\'],\n+ namespace_packages=["plone"],\n include_package_data=True,\n zip_safe=False,\n+ python_requires=">=3.8",\n install_requires=[\n- \'plone.staticresources\',\n- \'setuptools\',\n- \'six\',\n- \'zope.component\',\n- \'zope.interface\',\n- \'zope.publisher\',\n- \'zope.schema\',\n- \'Zope2\',\n+ "plone.base",\n+ "plone.resource",\n+ "plone.staticresources",\n+ "setuptools",\n+ "Products.CMFCore",\n+ "Zope",\n ],\n- extras_require={\n- \'test\': [\'plone.app.testing\']\n- },\n+ extras_require={"test": ["plone.app.testing"]},\n entry_points="""\n """,\n )\ndiff --git a/tox.ini b/tox.ini\nnew file mode 100644\nindex 0000000..fd8c81d\n--- /dev/null\n+++ b/tox.ini\n@@ -0,0 +1,76 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[tox]\n+# We need 4.4.0 for constrain_package_deps.\n+min_version = 4.4.0\n+envlist =\n+ format\n+ lint\n+ test\n+\n+[testenv]\n+allowlist_externals =\n+ sh\n+\n+[testenv:format]\n+description = automatically reformat code\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a pyupgrade\n+ pre-commit run -a isort\n+ pre-commit run -a black\n+ pre-commit run -a zpretty\n+\n+[testenv:lint]\n+description = run linters that will help improve the code style\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a\n+\n+[testenv:dependencies]\n+description = check if the package defines all its dependencies\n+skip_install = true\n+deps =\n+ build\n+ z3c.dependencychecker==2.11\n+commands =\n+ python -m build --sdist --no-isolation\n+ dependencychecker\n+\n+[testenv:dependencies-graph]\n+description = generate a graph out of the package\'s dependencies\n+deps =\n+ pipdeptree==2.5.1\n+ graphviz # optional dependency of pipdeptree\n+commands =\n+ sh -c \'pipdeptree --exclude setuptools,wheel,pipdeptree,zope.interface,zope.component --graph-output svg > dependencies.svg\'\n+\n+[testenv:test]\n+use_develop = true\n+constrain_package_deps = true\n+set_env = ROBOT_BROWSER=headlesschrome\n+deps =\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+commands =\n+ zope-testrunner --all --test-path={toxinidir} -s plone.resourceeditor {posargs}\n+extras =\n+ test\n+\n+[testenv:coverage]\n+use_develop = true\n+constrain_package_deps = true\n+set_env = ROBOT_BROWSER=headlesschrome\n+deps =\n+ coverage\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+commands =\n+ coverage run {envbindir}/zope-testrunner --all --test-path={toxinidir} -s plone.resourceeditor {posargs}\n+ coverage report -m --format markdown\n+extras =\n+ test\n' +b'diff --git a/news/93.feature b/news/93.feature\nnew file mode 100644\nindex 0000000..fe0847c\n--- /dev/null\n+++ b/news/93.feature\n@@ -0,0 +1,4 @@\n+Update the resourceRegistries ETag to use the config registry modification time.\n+This time is set since Plone 6.0.4.\n+Fixes `issue 93 `_.\n+[maurits]\ndiff --git a/plone/app/caching/operations/etags.py b/plone/app/caching/operations/etags.py\nindex 8187172..20f1783 100644\n--- a/plone/app/caching/operations/etags.py\n+++ b/plone/app/caching/operations/etags.py\n@@ -4,11 +4,10 @@\n from plone.app.caching.operations.utils import getContext\n from plone.app.caching.operations.utils import getLastModifiedAnnotation\n from plone.base.utils import safe_hasattr\n+from plone.registry.interfaces import IRegistry\n from Products.CMFCore.interfaces import ICatalogTool\n from Products.CMFCore.interfaces import IMembershipTool\n from Products.CMFCore.utils import getToolByName\n-from Products.CMFPlone.resources.utils import get_override_directory\n-from Products.CMFPlone.resources.utils import PRODUCTION_RESOURCE_DIRECTORY\n from zope.component import adapter\n from zope.component import queryMultiAdapter\n from zope.component import queryUtility\n@@ -19,6 +18,13 @@\n import time\n \n \n+try:\n+ # available since Plone 6.0.4\n+ from Products.CMFPlone.resources.browser.resource import _RESOURCE_REGISTRY_MTIME\n+except ImportError:\n+ _RESOURCE_REGISTRY_MTIME = None\n+\n+\n @implementer(IETagValue)\n @adapter(Interface, Interface)\n class UserID:\n@@ -239,22 +245,15 @@ def __init__(self, published, request):\n self.request = request\n \n def __call__(self):\n- context = getContext(self.published)\n- container = get_override_directory(context)\n- if PRODUCTION_RESOURCE_DIRECTORY not in container:\n+ if _RESOURCE_REGISTRY_MTIME is None:\n return ""\n- production_folder = container[PRODUCTION_RESOURCE_DIRECTORY]\n- filename = "timestamp.txt"\n- if filename not in production_folder:\n+ registry = queryUtility(IRegistry)\n+ if registry is None:\n return ""\n- timestamp = production_folder.readFile(filename)\n- if not timestamp:\n+ mtime = getattr(registry, _RESOURCE_REGISTRY_MTIME, None)\n+ if mtime is None:\n return ""\n- # timestamp is in bytes, and we must return a string.\n- # On Python 2 this is the same, but not on Python 3.\n- if not isinstance(timestamp, str):\n- timestamp = timestamp.decode("utf-8")\n- return timestamp\n+ return str(mtime)\n \n \n @implementer(IETagValue)\ndiff --git a/plone/app/caching/tests/test_profile_with_caching_proxy.py b/plone/app/caching/tests/test_profile_with_caching_proxy.py\nindex 8df999a..cdb62c9 100644\n--- a/plone/app/caching/tests/test_profile_with_caching_proxy.py\n+++ b/plone/app/caching/tests/test_profile_with_caching_proxy.py\n@@ -73,12 +73,12 @@ def test_composite_viewsxx(self):\n # Can we just call that test from this context?\n \n catalog = self.portal["portal_catalog"]\n- skins_tool = self.portal["portal_skins"]\n+ default_skin = self.portal["portal_skins"].default_skin\n \n # Add folder content\n setRoles(self.portal, TEST_USER_ID, ("Manager",))\n self.portal.invokeFactory("Folder", "f1")\n- self.portal["f1"].title = "one"\n+ self.portal["f1"].title = "Folder one"\n self.portal["f1"].description = "Folder one description"\n self.portal["f1"].reindexObject()\n \n@@ -105,9 +105,16 @@ def test_composite_viewsxx(self):\n # - turn on gzip?\n # - set skin? Maybe\n # - leave status unlocked\n- #\n+ # - set the mod date on the resource registries? Probably.\n transaction.commit()\n \n+ # Since Plone 6.0.4 we have a modification date on the registry.\n+ from Products.CMFPlone.resources.browser.resource import (\n+ _RESOURCE_REGISTRY_MTIME,\n+ )\n+\n+ mtime = str(getattr(self.registry, _RESOURCE_REGISTRY_MTIME))\n+\n # Request the authenticated folder\n now = stable_now()\n browser = Browser(self.app)\n@@ -124,10 +131,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"|test_user_1_|{catalog.getCounter()}|en|{skins_tool.default_skin}|0|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Set the copy/cut cookie and then request the folder view again\n@@ -141,10 +146,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"|test_user_1_|{catalog.getCounter()}|en|{skins_tool.default_skin}|0|1|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|1|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n \n # Request the authenticated page\n now = stable_now()\n@@ -163,10 +166,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"|test_user_1_|{catalog.getCounter()}|en|{skins_tool.default_skin}|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the authenticated page again -- to test RAM cache.\n@@ -205,7 +206,6 @@ def test_composite_viewsxx(self):\n # Request the anonymous folder\n now = stable_now()\n browser = Browser(self.app)\n- browser.handleErrors = False\n browser.open(self.portal["f1"].absolute_url())\n self.assertEqual("plone.content.folderView", browser.headers["X-Cache-Rule"])\n self.assertEqual(\n@@ -215,10 +215,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"||{catalog.getCounter()}|en|{skins_tool.default_skin}|0|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the anonymous page\n@@ -234,10 +232,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"||{catalog.getCounter()}|en|{skins_tool.default_skin}|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the anonymous page again -- to test RAM cache.\n@@ -257,10 +253,8 @@ def test_composite_viewsxx(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- self.assertEqual(\n- f\'"||{catalog.getCounter()}|en|{skins_tool.default_skin}|0|"\',\n- normalize_etag(browser.headers["ETag"]),\n- )\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n+ self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n # Request the anonymous page again -- with an INM header to test 304.\ndiff --git a/plone/app/caching/tests/test_profile_without_caching_proxy.py b/plone/app/caching/tests/test_profile_without_caching_proxy.py\nindex 07a0585..3d9076a 100644\n--- a/plone/app/caching/tests/test_profile_without_caching_proxy.py\n+++ b/plone/app/caching/tests/test_profile_without_caching_proxy.py\n@@ -92,6 +92,13 @@ def test_composite_views(self):\n # - set the mod date on the resource registries? Probably.\n transaction.commit()\n \n+ # Since Plone 6.0.4 we have a modification date on the registry.\n+ from Products.CMFPlone.resources.browser.resource import (\n+ _RESOURCE_REGISTRY_MTIME,\n+ )\n+\n+ mtime = str(getattr(self.registry, _RESOURCE_REGISTRY_MTIME))\n+\n # Request the authenticated folder\n now = stable_now()\n browser = Browser(self.app)\n@@ -108,7 +115,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|0|"\'\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -123,7 +130,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|1|"\'\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|1|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n \n # Request the authenticated page\n@@ -143,7 +150,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|"\'\n+ tag = f\'"|test_user_1_|{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -192,7 +199,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|0|"\'\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -209,7 +216,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|"\'\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n@@ -230,7 +237,7 @@ def test_composite_views(self):\n self.assertEqual(\n "max-age=0, must-revalidate, private", browser.headers["Cache-Control"]\n )\n- tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|"\'\n+ tag = f\'"||{catalog.getCounter()}|en|{default_skin}|0|{mtime}"\'\n self.assertEqual(tag, normalize_etag(browser.headers["ETag"]))\n self.assertGreater(now, dateutil.parser.parse(browser.headers["Expires"]))\n \n'