diff --git a/last_commit.txt b/last_commit.txt index 76d0ce217c..a43fad7218 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,21 +1,94 @@ -Repository: plone.restapi +Repository: Products.CMFPlacefulWorkflow Branch: refs/heads/master -Date: 2023-01-26T08:05:50-08:00 -Author: David Glick (davisagli) -Commit: https://github.com/plone/plone.restapi/commit/44b7885a881aaac022933cf9fedcb60e9029facd +Date: 2023-01-26T11:33:38+01:00 +Author: Maurits van Rees (mauritsvanrees) +Commit: https://github.com/plone/Products.CMFPlacefulWorkflow/commit/6b33fd9d202cd410f8733cb6f34e037e93fb026c -Fix auth cookie for Zope users in login endpoint (#1573) +Configuring with plone/meta -* Fix auth cookie for Zope users in login endpoint - -* lint +Files changed: +A .editorconfig +A .github/workflows/linting.yml +A lint-requirements.txt +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/.github/workflows/linting.yml b/.github/workflows/linting.yml\nnew file mode 100644\nindex 0000000..a2139a8\n--- /dev/null\n+++ b/.github/workflows/linting.yml\n@@ -0,0 +1,39 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+name: Linting\n+on:\n+ push:\n+ branches: [master]\n+ pull_request:\n+ branches: [master]\n+ workflow_dispatch:\n+\n+jobs:\n+ test:\n+ name: Lint code\n+ runs-on: ${{ matrix.os }}\n+ strategy:\n+ matrix:\n+ python-version: ["3.8"]\n+ os: ["ubuntu-22.04"]\n+ steps:\n+ - uses: actions/checkout@v3\n+ - name: Set up Python\n+ uses: actions/setup-python@v4\n+ with:\n+ python-version: ${{ matrix.python-version }}\n+ - name: Cache packages\n+ uses: actions/cache@v3\n+ with:\n+ path: ~/.cache/pip\n+ key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles(\'lint-requirements.txt\', \'tox.ini\') }}\n+ restore-keys: |\n+ ${{ runner.os }}-pip-${{ matrix.python-version }}-\n+ ${{ runner.os }}-pip-\n+ - name: Install dependencies\n+ run: python -m pip install tox\n+ - name: Run formatters\n+ run: tox -e format\n+ # linters\n+ - name: QA\n+ run: tox -e lint\ndiff --git a/lint-requirements.txt b/lint-requirements.txt\nnew file mode 100644\nindex 0000000..129db4d\n--- /dev/null\n+++ b/lint-requirements.txt\n@@ -0,0 +1,8 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+black==22.12.0\n+check-manifest==0.49\n+codespell==2.2.2\n+flake8==6.0.0\n+isort==5.11.4\n+pyupgrade==3.3.1\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..0f96c85 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,24 @@ 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"]\ndiff --git a/setup.cfg b/setup.cfg\nindex 3958a6a..3551aed 100644\n--- a/setup.cfg\n+++ b/setup.cfg\n@@ -1,32 +1,27 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n [metadata]\n long_description = file: README.rst, CHANGES.rst\n \n-[check-manifest]\n-ignore =\n- *.txt\n- *.cfg\n-\n [bdist_wheel]\n universal = 0\n \n-[isort]\n-profile = black\n-force_alphabetical_sort=True\n-force_single_line=True\n-lines_after_imports=2\n-\n [flake8]\n-# black compatible flake8 rules:\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- C812,\n- E501\n- T001\n- C813\n-# E203, E266\n-exclude = bootstrap.py,docs,*.egg.,omelette\n-max-line-length = 88\n-max-complexity = 18\n-select = B,C,E,F,W,T4,B9\n+ # black takes care of spaces within slicing (list[:])\n+ E203,\n+ # black takes care of spaces after commas\n+ E231,\n \n-builtins = unicode,basestring\n+[check-manifest]\n+ignore =\n+ .editorconfig\n+ .meta.toml\n+ tox.ini\n+ *.txt\n+ *.cfg\ndiff --git a/tox.ini b/tox.ini\nnew file mode 100644\nindex 0000000..5d1a605\n--- /dev/null\n+++ b/tox.ini\n@@ -0,0 +1,39 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[tox]\n+envlist =\n+ format\n+ lint\n+\n+[testenv]\n+py_files = git ls-files "*.py"\n+text_files = git ls-files "*.rst" "*.md"\n+allowlist_externals =\n+ sh\n+\n+[testenv:format]\n+description = automatically reformat python code\n+skip_install = true\n+deps =\n+ pyupgrade\n+ isort\n+ black\n+ -c lint-requirements.txt\n+commands =\n+ sh -c \'{[testenv]py_files} | xargs pyupgrade --py38-plus\'\n+ sh -c \'{[testenv]py_files} | xargs isort\'\n+ sh -c \'{[testenv]py_files} | xargs black\'\n+\n+[testenv:lint]\n+description = run linters that will help improve the code style\n+skip_install = true\n+deps =\n+ flake8\n+ codespell\n+ check-manifest\n+ -c lint-requirements.txt\n+commands =\n+ sh -c \'{[testenv]py_files} | xargs flake8\'\n+ sh -c \'{[testenv]py_files} | xargs codespell\'\n+ sh -c \'{[testenv]text_files} | xargs codespell\'\n+ check-manifest\n' + +Repository: Products.CMFPlacefulWorkflow + + +Branch: refs/heads/master +Date: 2023-01-26T11:35:35+01:00 +Author: Maurits van Rees (mauritsvanrees) +Commit: https://github.com/plone/Products.CMFPlacefulWorkflow/commit/b5c1c3e827bec312107ec42e5ec0a305704f2699 + +Add .meta.toml + +Files changed: +A .meta.toml +M Products/CMFPlacefulWorkflow/tests/test_configlet.py + +b'diff --git a/.meta.toml b/.meta.toml\nnew file mode 100644\nindex 0000000..82879b6\n--- /dev/null\n+++ b/.meta.toml\n@@ -0,0 +1,14 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[meta]\n+template = "default"\n+commit-id = "14c035e5"\n+\n+[check-manifest]\n+additional-ignores = [\n+ "*.txt",\n+ "*.cfg",\n+ ]\n+\n+[setup.metadata]\n+long_description = "file: README.rst, CHANGES.rst"\ndiff --git a/Products/CMFPlacefulWorkflow/tests/test_configlet.py b/Products/CMFPlacefulWorkflow/tests/test_configlet.py\nindex e11d2eb..d6e79ed 100644\n--- a/Products/CMFPlacefulWorkflow/tests/test_configlet.py\n+++ b/Products/CMFPlacefulWorkflow/tests/test_configlet.py\n@@ -48,7 +48,7 @@ def getBrowser(self, logged_in=False):\n # credentials """\n browser.addHeader(\n "Authorization",\n- "Basic {}:{}".format(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n+ f"Basic {SITE_OWNER_NAME}:{SITE_OWNER_PASSWORD}",\n )\n return browser\n \n' + +Repository: Products.CMFPlacefulWorkflow + + +Branch: refs/heads/master +Date: 2023-01-26T11:36:04+01:00 +Author: Maurits van Rees (mauritsvanrees) +Commit: https://github.com/plone/Products.CMFPlacefulWorkflow/commit/6e8dae7f5e50771abd5d1f20e8362700be8ca5ff + +codespell fixes + +Files changed: +M CHANGES.rst +M Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py + +b'diff --git a/CHANGES.rst b/CHANGES.rst\nindex 316e587..846125e 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -431,7 +431,7 @@ Fixes:\n Specifically, allow_any was set to False, which bit me.\n [maurits]\n \n-- Made worflow policies translatable in prefs_workflow_localpolicies_form.\n+- Made workflow policies translatable in prefs_workflow_localpolicies_form.\n [vincentfretin]\n \n 1.4.2 - 2009-03-05\ndiff --git a/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py b/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py\nindex ada3bc0..be399b2 100644\n--- a/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py\n+++ b/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py\n@@ -191,7 +191,7 @@ def getChainFor(self, ob, managescreen=False):\n If chain doesn\'t exist we return None to get a fallback from\n portal_workflow.\n \n- @parm managescreen: If True return the special tuple\n+ @param managescreen: If True return the special tuple\n (\'default\') instead of the actual default\n chain (hack).\n """\n@@ -269,9 +269,9 @@ def setChain(self, portal_type, chain, REQUEST=None):\n \n @type chain: tuple of strings or None\n @param chain: A tuple of workflow ids to be set for the portal type.\n- A few special values exsist:\n+ A few special values exist:\n - C{None}: Acquire chain from a policy above,\n- ultimatly from the portal workflow settings.\n+ ultimately from the portal workflow settings.\n - C{()} (empty tuple): No workflow for this type.\n - C{(\'default\',)}: Use the configured default workflow.\n """\n' + +Repository: Products.CMFPlacefulWorkflow + + +Branch: refs/heads/master +Date: 2023-01-26T11:48:20+01:00 +Author: Maurits van Rees (mauritsvanrees) +Commit: https://github.com/plone/Products.CMFPlacefulWorkflow/commit/49c07d120b65059cda9d205db7f65998547cb6d6 + +Add news snippet. + +Files changed: +A news/1.internal + +b'diff --git a/news/1.internal b/news/1.internal\nnew file mode 100644\nindex 0000000..4e4ec23\n--- /dev/null\n+++ b/news/1.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone meta]\n' + +Repository: Products.CMFPlacefulWorkflow + + +Branch: refs/heads/master +Date: 2023-01-26T18:08:29+01:00 +Author: Gil Forcada Codinachs (gforcada) +Commit: https://github.com/plone/Products.CMFPlacefulWorkflow/commit/8dccf7a57366bfa0b6da8a55bd41c2eec5f751dc + +Merge pull request #45 from plone/config-with-default-template-6e8dae7f + +Config with default template 6e8dae7f Files changed: -A news/1572.bugfix -M src/plone/restapi/services/auth/login.py -M src/plone/restapi/tests/test_functional_auth.py +A .editorconfig +A .github/workflows/linting.yml +A .meta.toml +A lint-requirements.txt +A news/1.internal +A tox.ini +M CHANGES.rst +M Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py +M Products/CMFPlacefulWorkflow/tests/test_configlet.py +M pyproject.toml +M setup.cfg -b'diff --git a/news/1572.bugfix b/news/1572.bugfix\nnew file mode 100644\nindex 000000000..316c6b1a6\n--- /dev/null\n+++ b/news/1572.bugfix\n@@ -0,0 +1 @@\n+Fix bug where the `@login` endpoint did not set the correct `__ac` cookie for Zope users. [davisagli]\ndiff --git a/src/plone/restapi/services/auth/login.py b/src/plone/restapi/services/auth/login.py\nindex 47813440d..4bc47df6e 100644\n--- a/src/plone/restapi/services/auth/login.py\n+++ b/src/plone/restapi/services/auth/login.py\n@@ -32,6 +32,11 @@ def reply(self):\n password = data["password"]\n uf = self._find_userfolder(userid)\n \n+ # Also put the password in __ac_password on the request.\n+ # The post-login code in PlonePAS expects to find it there\n+ # when it calls the PAS updateCredentials plugin.\n+ self.request.form["__ac_password"] = data["password"]\n+\n if uf is not None:\n plugins = uf._getOb("plugins")\n authenticators = plugins.listPlugins(IAuthenticationPlugin)\ndiff --git a/src/plone/restapi/tests/test_functional_auth.py b/src/plone/restapi/tests/test_functional_auth.py\nindex e308c903f..ea2f5f470 100644\n--- a/src/plone/restapi/tests/test_functional_auth.py\n+++ b/src/plone/restapi/tests/test_functional_auth.py\n@@ -1,4 +1,3 @@\n-from plone.app.testing import login\n from plone.app.testing import setRoles\n from plone.app.testing import SITE_OWNER_NAME\n from plone.app.testing import SITE_OWNER_PASSWORD\n@@ -21,7 +20,6 @@ def setUp(self):\n self.portal = self.layer["portal"]\n self.portal_url = self.portal.absolute_url()\n setRoles(self.portal, TEST_USER_ID, ["Manager"])\n- login(self.portal, SITE_OWNER_NAME)\n self.private_document = self.portal[\n self.portal.invokeFactory("Document", id="doc1", title="My Document")\n ]\n@@ -68,12 +66,21 @@ def test_api_login_grants_zmi(self):\n """\n Logging in via the API also grants access to the Zope root ZMI.\n """\n+ app = self.layer["app"]\n+ app.acl_users.plugins.users.addUser(\n+ "zopeuser",\n+ "zopeuser",\n+ TEST_USER_PASSWORD,\n+ )\n+ app.acl_users.plugins.roles.assignRoleToPrincipal("Manager", "zopeuser")\n+ transaction.commit()\n+\n session = requests.Session()\n self.addCleanup(session.close)\n login_resp = session.post(\n self.portal_url + "/@login",\n headers={"Accept": "application/json"},\n- json={"login": SITE_OWNER_NAME, "password": SITE_OWNER_PASSWORD},\n+ json={"login": "zopeuser", "password": TEST_USER_PASSWORD},\n )\n self.assertIn(\n "__ac",\n@@ -94,11 +101,6 @@ def test_api_login_grants_zmi(self):\n zmi_resp = session.get(\n self.layer["app"].absolute_url() + "/manage_workspace",\n )\n- # Works in the browser when running `$ bin/instance fg` in a `plone.restapi`\n- # checkout against `http://localhost:8080/manage_main` but doesn\'t work in the\n- # browser against the test fixture at `http://localhost:55001/manage_main`. My\n- # guess is that there\'s some subtle difference in the PAS plugin configuration.\n- self.skipTest("FIXME: Works in real instance but not test fixture")\n self.assertEqual(\n zmi_resp.status_code,\n 200,\n' +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/.github/workflows/linting.yml b/.github/workflows/linting.yml\nnew file mode 100644\nindex 0000000..a2139a8\n--- /dev/null\n+++ b/.github/workflows/linting.yml\n@@ -0,0 +1,39 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+name: Linting\n+on:\n+ push:\n+ branches: [master]\n+ pull_request:\n+ branches: [master]\n+ workflow_dispatch:\n+\n+jobs:\n+ test:\n+ name: Lint code\n+ runs-on: ${{ matrix.os }}\n+ strategy:\n+ matrix:\n+ python-version: ["3.8"]\n+ os: ["ubuntu-22.04"]\n+ steps:\n+ - uses: actions/checkout@v3\n+ - name: Set up Python\n+ uses: actions/setup-python@v4\n+ with:\n+ python-version: ${{ matrix.python-version }}\n+ - name: Cache packages\n+ uses: actions/cache@v3\n+ with:\n+ path: ~/.cache/pip\n+ key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles(\'lint-requirements.txt\', \'tox.ini\') }}\n+ restore-keys: |\n+ ${{ runner.os }}-pip-${{ matrix.python-version }}-\n+ ${{ runner.os }}-pip-\n+ - name: Install dependencies\n+ run: python -m pip install tox\n+ - name: Run formatters\n+ run: tox -e format\n+ # linters\n+ - name: QA\n+ run: tox -e lint\ndiff --git a/.meta.toml b/.meta.toml\nnew file mode 100644\nindex 0000000..82879b6\n--- /dev/null\n+++ b/.meta.toml\n@@ -0,0 +1,14 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[meta]\n+template = "default"\n+commit-id = "14c035e5"\n+\n+[check-manifest]\n+additional-ignores = [\n+ "*.txt",\n+ "*.cfg",\n+ ]\n+\n+[setup.metadata]\n+long_description = "file: README.rst, CHANGES.rst"\ndiff --git a/CHANGES.rst b/CHANGES.rst\nindex 316e587..846125e 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -431,7 +431,7 @@ Fixes:\n Specifically, allow_any was set to False, which bit me.\n [maurits]\n \n-- Made worflow policies translatable in prefs_workflow_localpolicies_form.\n+- Made workflow policies translatable in prefs_workflow_localpolicies_form.\n [vincentfretin]\n \n 1.4.2 - 2009-03-05\ndiff --git a/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py b/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py\nindex ada3bc0..be399b2 100644\n--- a/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py\n+++ b/Products/CMFPlacefulWorkflow/DefaultWorkflowPolicy.py\n@@ -191,7 +191,7 @@ def getChainFor(self, ob, managescreen=False):\n If chain doesn\'t exist we return None to get a fallback from\n portal_workflow.\n \n- @parm managescreen: If True return the special tuple\n+ @param managescreen: If True return the special tuple\n (\'default\') instead of the actual default\n chain (hack).\n """\n@@ -269,9 +269,9 @@ def setChain(self, portal_type, chain, REQUEST=None):\n \n @type chain: tuple of strings or None\n @param chain: A tuple of workflow ids to be set for the portal type.\n- A few special values exsist:\n+ A few special values exist:\n - C{None}: Acquire chain from a policy above,\n- ultimatly from the portal workflow settings.\n+ ultimately from the portal workflow settings.\n - C{()} (empty tuple): No workflow for this type.\n - C{(\'default\',)}: Use the configured default workflow.\n """\ndiff --git a/Products/CMFPlacefulWorkflow/tests/test_configlet.py b/Products/CMFPlacefulWorkflow/tests/test_configlet.py\nindex e11d2eb..d6e79ed 100644\n--- a/Products/CMFPlacefulWorkflow/tests/test_configlet.py\n+++ b/Products/CMFPlacefulWorkflow/tests/test_configlet.py\n@@ -48,7 +48,7 @@ def getBrowser(self, logged_in=False):\n # credentials """\n browser.addHeader(\n "Authorization",\n- "Basic {}:{}".format(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n+ f"Basic {SITE_OWNER_NAME}:{SITE_OWNER_PASSWORD}",\n )\n return browser\n \ndiff --git a/lint-requirements.txt b/lint-requirements.txt\nnew file mode 100644\nindex 0000000..129db4d\n--- /dev/null\n+++ b/lint-requirements.txt\n@@ -0,0 +1,8 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+black==22.12.0\n+check-manifest==0.49\n+codespell==2.2.2\n+flake8==6.0.0\n+isort==5.11.4\n+pyupgrade==3.3.1\ndiff --git a/news/1.internal b/news/1.internal\nnew file mode 100644\nindex 0000000..4e4ec23\n--- /dev/null\n+++ b/news/1.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone meta]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..0f96c85 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,24 @@ 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"]\ndiff --git a/setup.cfg b/setup.cfg\nindex 3958a6a..3551aed 100644\n--- a/setup.cfg\n+++ b/setup.cfg\n@@ -1,32 +1,27 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n [metadata]\n long_description = file: README.rst, CHANGES.rst\n \n-[check-manifest]\n-ignore =\n- *.txt\n- *.cfg\n-\n [bdist_wheel]\n universal = 0\n \n-[isort]\n-profile = black\n-force_alphabetical_sort=True\n-force_single_line=True\n-lines_after_imports=2\n-\n [flake8]\n-# black compatible flake8 rules:\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- C812,\n- E501\n- T001\n- C813\n-# E203, E266\n-exclude = bootstrap.py,docs,*.egg.,omelette\n-max-line-length = 88\n-max-complexity = 18\n-select = B,C,E,F,W,T4,B9\n+ # black takes care of spaces within slicing (list[:])\n+ E203,\n+ # black takes care of spaces after commas\n+ E231,\n \n-builtins = unicode,basestring\n+[check-manifest]\n+ignore =\n+ .editorconfig\n+ .meta.toml\n+ tox.ini\n+ *.txt\n+ *.cfg\ndiff --git a/tox.ini b/tox.ini\nnew file mode 100644\nindex 0000000..5d1a605\n--- /dev/null\n+++ b/tox.ini\n@@ -0,0 +1,39 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/master/config/default\n+[tox]\n+envlist =\n+ format\n+ lint\n+\n+[testenv]\n+py_files = git ls-files "*.py"\n+text_files = git ls-files "*.rst" "*.md"\n+allowlist_externals =\n+ sh\n+\n+[testenv:format]\n+description = automatically reformat python code\n+skip_install = true\n+deps =\n+ pyupgrade\n+ isort\n+ black\n+ -c lint-requirements.txt\n+commands =\n+ sh -c \'{[testenv]py_files} | xargs pyupgrade --py38-plus\'\n+ sh -c \'{[testenv]py_files} | xargs isort\'\n+ sh -c \'{[testenv]py_files} | xargs black\'\n+\n+[testenv:lint]\n+description = run linters that will help improve the code style\n+skip_install = true\n+deps =\n+ flake8\n+ codespell\n+ check-manifest\n+ -c lint-requirements.txt\n+commands =\n+ sh -c \'{[testenv]py_files} | xargs flake8\'\n+ sh -c \'{[testenv]py_files} | xargs codespell\'\n+ sh -c \'{[testenv]text_files} | xargs codespell\'\n+ check-manifest\n'