From 2535ed0939a291b4f5fd5ff3f9d2554fcaa26c46 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Sun, 25 Jun 2023 11:33:05 +0200 Subject: [PATCH 1/7] Configuring with plone/meta --- .editorconfig | 14 +++ .flake8 | 22 +++++ .github/workflows/meta.yml | 28 ++++++ .gitignore | 62 +++++++++--- .meta.toml | 8 +- .pre-commit-config.yaml | 84 ++++++++++++++++ news/cfffba8c.internal | 2 + pyproject.toml | 115 +++++++++++++++++++++- tox.ini | 194 +++++++++++++++++++++++++++++-------- 9 files changed, 469 insertions(+), 60 deletions(-) create mode 100644 .flake8 create mode 100644 .github/workflows/meta.yml create mode 100644 .pre-commit-config.yaml create mode 100644 news/cfffba8c.internal diff --git a/.editorconfig b/.editorconfig index b4158b8..919b411 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,6 @@ # Generated from: # https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file # # EditorConfig Configuration file, for more details see: # http://EditorConfig.org @@ -32,8 +33,21 @@ indent_size = 4 # 2 space indentation indent_size = 2 +[*.{json,jsonl,js,jsx,ts,tsx,css,less,scss,html}] # Frontend development +# 2 space indentation +indent_size = 2 + [{Makefile,.gitmodules}] # Tab indentation (no size specified, but view as 4 spaces) indent_style = tab indent_size = unset tab_width = unset + + +## +# Add extra configuration options in .meta.toml: +# [editorconfig] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..7ef4f64 --- /dev/null +++ b/.flake8 @@ -0,0 +1,22 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[flake8] +doctests = 1 +ignore = + # black takes care of line length + E501, + # black takes care of where to break lines + W503, + # black takes care of spaces within slicing (list[:]) + E203, + # black takes care of spaces after commas + E231, + +## +# Add extra configuration options in .meta.toml: +# [flake8] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/.github/workflows/meta.yml b/.github/workflows/meta.yml new file mode 100644 index 0000000..a7f25e0 --- /dev/null +++ b/.github/workflows/meta.yml @@ -0,0 +1,28 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +name: Meta +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + workflow_dispatch: + +jobs: + qa: + uses: plone/meta/.github/workflows/qa.yml@main + test: + uses: plone/meta/.github/workflows/test.yml@main + coverage: + uses: plone/meta/.github/workflows/coverage.yml@main + dependencies: + uses: plone/meta/.github/workflows/dependencies.yml@main + release-ready: + uses: plone/meta/.github/workflows/release_ready.yml@main + circular: + uses: plone/meta/.github/workflows/circular.yml@main diff --git a/.gitignore b/.gitignore index 23adb66..81594fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,51 @@ -*.py[co] -/borg.localrole.egg-info/ -*.mo -build -dist -.project -.pydevproject -.settings +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +# python related +*.egg-info +*.pyc +*.pyo + +# tools related +build/ +.coverage +coverage.xml +dist/ +docs/_build +__pycache__/ +.tox +.vscode/ +node_modules/ + +# venv / buildout related +bin/ +develop-eggs/ +eggs/ +.eggs/ +etc/ .installed.cfg -/bin -/develop-eggs -/parts -*.bak +include/ +lib/ +lib64 +.mr.developer.cfg +parts/ +pyvenv.cfg +var/ + +# mxdev +/instance/ +/.make-sentinels/ +/*-mxdev.txt +/reports/ +/sources/ +/venv/ +.installed.txt + + +## +# Add extra configuration options in .meta.toml: +# [gitignore] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/.meta.toml b/.meta.toml index 642e0d7..4ca2970 100644 --- a/.meta.toml +++ b/.meta.toml @@ -1,10 +1,6 @@ # Generated from: # https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file [meta] template = "default" -commit-id = "68486a87" - -[dependencies] -mappings = [ - "Zope = ['Products.PageTemplates', 'ZPublisher', ]", - ] +commit-id = "cfffba8c" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7de9fd7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,84 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +ci: + autofix_prs: false + autoupdate_schedule: monthly + +repos: +- repo: https://github.com/asottile/pyupgrade + rev: v3.4.0 + hooks: + - id: pyupgrade + args: [--py38-plus] +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +- repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black +- repo: https://github.com/collective/zpretty + rev: 3.1.0a2 + hooks: + - id: zpretty + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# zpretty_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# flake8_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/codespell-project/codespell + rev: v2.2.4 + hooks: + - id: codespell + additional_dependencies: + - tomli + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# codespell_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/mgedmin/check-manifest + rev: "0.49" + hooks: + - id: check-manifest +- repo: https://github.com/regebro/pyroma + rev: "4.2" + hooks: + - id: pyroma +- repo: https://github.com/mgedmin/check-python-versions + rev: "0.21.2" + hooks: + - id: check-python-versions + args: ['--only', 'setup.py,pyproject.toml'] +- repo: https://github.com/collective/i18ndude + rev: "6.0.0" + hooks: + - id: i18ndude + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/news/cfffba8c.internal b/news/cfffba8c.internal new file mode 100644 index 0000000..c08f539 --- /dev/null +++ b/news/cfffba8c.internal @@ -0,0 +1,2 @@ +Update configuration files. +[plone devs] diff --git a/pyproject.toml b/pyproject.toml index 7ff4077..e442bd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,9 @@ # Generated from: # https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file [tool.towncrier] -filename = "CHANGES.rst" directory = "news/" +filename = "CHANGES.rst" title_format = "{version} ({project_date})" underlines = ["-", ""] @@ -36,11 +37,121 @@ directory = "tests" name = "Tests" showcontent = true +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# towncrier_extra_lines = """ +# extra_configuration +# """ +## + [tool.isort] profile = "plone" +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# isort_extra_lines = """ +# extra_configuration +# """ +## + [tool.black] target-version = ["py38"] +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# black_extra_lines = """ +# extra_configuration +# """ +## + +[tool.codespell] +ignore-words-list = "discreet," +skip = "*.po," +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# codespell_ignores = "foo,bar" +# codespell_skip = "*.po,*.map,package-lock.json" +## + [tool.dependencychecker] -Zope = ['Products.PageTemplates', 'ZPublisher', ] +Zope = [ + # Zope own provided namespaces + 'App', 'OFS', 'Products.Five', 'Products.OFSP', 'Products.PageTemplates', + 'Products.SiteAccess', 'Shared', 'Testing', 'ZPublisher', 'ZTUtils', + 'Zope2', 'webdav', 'zmi', + # ExtensionClass own provided namespaces + 'ExtensionClass', 'ComputedAttribute', 'MethodObject', + # Zope dependencies + 'AccessControl', 'Acquisition', 'AuthEncoding', 'beautifulsoup4', 'BTrees', + 'cffi', 'Chameleon', 'DateTime', 'DocumentTemplate', + 'MultiMapping', 'multipart', 'PasteDeploy', 'Persistence', 'persistent', + 'pycparser', 'python-gettext', 'pytz', 'RestrictedPython', 'roman', + 'soupsieve', 'transaction', 'waitress', 'WebOb', 'WebTest', 'WSGIProxy2', + 'z3c.pt', 'zc.lockfile', 'ZConfig', 'zExceptions', 'ZODB', 'zodbpickle', + 'zope.annotation', 'zope.browser', 'zope.browsermenu', 'zope.browserpage', + 'zope.browserresource', 'zope.cachedescriptors', 'zope.component', + 'zope.configuration', 'zope.container', 'zope.contentprovider', + 'zope.contenttype', 'zope.datetime', 'zope.deferredimport', + 'zope.deprecation', 'zope.dottedname', 'zope.event', 'zope.exceptions', + 'zope.filerepresentation', 'zope.globalrequest', 'zope.hookable', + 'zope.i18n', 'zope.i18nmessageid', 'zope.interface', 'zope.lifecycleevent', + 'zope.location', 'zope.pagetemplate', 'zope.processlifetime', 'zope.proxy', + 'zope.ptresource', 'zope.publisher', 'zope.schema', 'zope.security', + 'zope.sequencesort', 'zope.site', 'zope.size', 'zope.structuredtext', + 'zope.tal', 'zope.tales', 'zope.testbrowser', 'zope.testing', + 'zope.traversing', 'zope.viewlet' +] +'Products.CMFCore' = [ + 'docutils', 'five.localsitemanager', 'Missing', 'Products.BTreeFolder2', + 'Products.GenericSetup', 'Products.MailHost', 'Products.PythonScripts', + 'Products.StandardCacheManagers', 'Products.ZCatalog', 'Record', + 'zope.sendmail', 'Zope' +] +'plone.base' = [ + 'plone.batching', 'plone.registry', 'plone.schema','plone.z3cform', + 'Products.CMFCore', 'Products.CMFDynamicViewFTI', +] +python-dateutil = ['dateutil'] + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# dependencies_ignores = "['zestreleaser.towncrier']" +# dependencies_mappings = [ +# "gitpython = ['git']", +# "pygithub = ['github']", +# ] +# """ +## + +[tool.check-manifest] +ignore = [ + ".editorconfig", + ".meta.toml", + ".pre-commit-config.yaml", + "tox.ini", + ".flake8", + "mx.ini", + +] +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# check_manifest_ignores = """ +# "*.map.js", +# "*.pyc", +# """ +## + + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/tox.ini b/tox.ini index 5a29bed..a4303c0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,75 +1,189 @@ # Generated from: # https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file [tox] +# We need 4.4.0 for constrain_package_deps. +min_version = 4.4.0 envlist = - format lint + test + dependencies + + +## +# Add extra configuration options in .meta.toml: +# [tox] +# envlist_lines = """ +# my_other_environment +# """ +# config_lines = """ +# my_extra_top_level_tox_configuration_lines +# """ +## [testenv] -py_files = git ls-files "*.py" -text_files = git ls-files "*.rst" "*.md" -xml_files = git ls-files "*.xml" -zcml_files = git ls-files "*.zcml" +skip_install = true allowlist_externals = - sh + echo + false +# Make sure typos like `tox -e formaat` are caught instead of silently doing nothing. +# See https://github.com/tox-dev/tox/issues/2858. +commands = + echo "Unrecognized environment name {envname}" + false [testenv:format] -description = automatically reformat python code +description = automatically reformat code skip_install = true deps = - pyupgrade - isort - black - zpretty - -c lint-requirements.txt + pre-commit commands = - sh -c '{[testenv]py_files} | xargs pyupgrade --py38-plus' - sh -c '{[testenv]py_files} | xargs isort' - sh -c '{[testenv]py_files} | xargs black' - sh -c '{[testenv]xml_files} | xargs zpretty -x -i || true' - sh -c '{[testenv]zcml_files} | xargs zpretty -z -i || true' + pre-commit run -a pyupgrade + pre-commit run -a isort + pre-commit run -a black + pre-commit run -a zpretty [testenv:lint] description = run linters that will help improve the code style skip_install = true deps = - flake8 - codespell - tomli # needed for codespell to read pyproject.toml - check-manifest - pyroma - -c lint-requirements.txt + pre-commit commands = - sh -c '{[testenv]py_files} | xargs flake8' - sh -c '{[testenv]py_files} | xargs codespell' - sh -c '{[testenv]text_files} | xargs codespell' - check-manifest - pyroma -n 10 . + pre-commit run -a [testenv:dependencies] description = check if the package defines all its dependencies +skip_install = true deps = - z3c.dependencychecker - -c lint-requirements.txt + build + z3c.dependencychecker==2.11 commands = + python -m build --sdist --no-isolation dependencychecker [testenv:dependencies-graph] -description = generate a graph with the distribution dependencies +description = generate a graph out of the dependencies of the package +skip_install = false +allowlist_externals = + sh deps = - pipdeptree + pipdeptree==2.5.1 graphviz # optional dependency of pipdeptree - -c lint-requirements.txt commands = - sh -c 'pipdeptree --exclude setuptools,pipdeptree,wheel --graph-output svg > dependencies.svg' + sh -c 'pipdeptree --exclude setuptools,wheel,pipdeptree,zope.interface,zope.component --graph-output svg > dependencies.svg' [testenv:test] -description = run the distribution's tests +description = run the distribution tests +use_develop = true +skip_install = false +constrain_package_deps = true +set_env = + ROBOT_BROWSER=headlesschrome + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +## +deps = + zope.testrunner + -c https://dist.plone.org/release/6.0-dev/constraints.txt +## +# Specify a custom constraints file in .meta.toml: +# [tox] +# constraints_file = "https://my-server.com/constraints.txt" +## +commands = + zope-testrunner --all --test-path={toxinidir} -s borg.localrole {posargs} +extras = + test + +## +# Add extra configuration options in .meta.toml: +# [tox] +# test_extras = """ +# tests +# widgets +# """ +## + +[testenv:coverage] +description = get a test coverage report +use_develop = true +skip_install = false +constrain_package_deps = true +set_env = + ROBOT_BROWSER=headlesschrome + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +## deps = - borg.localrole[test] - pytest - gocept.pytestlayer + coverage + zope.testrunner -c https://dist.plone.org/release/6.0-dev/constraints.txt commands = - pip install -e . - pytest + coverage run --branch --source borg.localrole {envbindir}/zope-testrunner --quiet --all --test-path={toxinidir} -s borg.localrole {posargs} + coverage report -m --format markdown + coverage xml +extras = + test + + +[testenv:release-check] +description = ensure that the distribution is ready to release +skip_install = true +deps = + twine + build + towncrier + -c https://dist.plone.org/release/6.0-dev/constraints.txt +commands = + # fake version to not have to install the package + # we build the change log as news entries might break + # the README that is displayed on PyPI + towncrier build --version=100.0.0 --yes + python -m build --sdist --no-isolation + twine check dist/* + +[testenv:circular] +description = ensure there are no cyclic dependencies +use_develop = true +skip_install = false +set_env = + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +## +allowlist_externals = + sh +deps = + pipdeptree + pipforester + -c https://dist.plone.org/release/6.0-dev/constraints.txt +commands = + # Generate the full dependency tree + sh -c 'pipdeptree -j > forest.json' + # Generate a DOT graph with the circular dependencies, if any + pipforester -i forest.json -o forest.dot --cycles + # Report if there are any circular dependencies, i.e. error if there are any + pipforester -i forest.json --check-cycles -o /dev/null + + +## +# Add extra configuration options in .meta.toml: +# [tox] +# extra_lines = """ +# _your own configuration lines_ +# """ +## From a9347e12865055d849d07315e52d53a26c80e455 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Sun, 25 Jun 2023 11:33:38 +0200 Subject: [PATCH 2/7] cleanup: remove unused file --- lint-requirements.txt | 12 ------------ setup.cfg | 23 ----------------------- 2 files changed, 35 deletions(-) delete mode 100644 lint-requirements.txt delete mode 100644 setup.cfg diff --git a/lint-requirements.txt b/lint-requirements.txt deleted file mode 100644 index 2a91911..0000000 --- a/lint-requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Generated from: -# https://github.com/plone/meta/tree/master/config/default -black==22.12.0 -check-manifest==0.49 -codespell==2.2.2 -flake8==6.0.0 -isort==5.11.4 -pipdeptree==2.3.3 -pyroma==4.1 -pyupgrade==3.3.1 -z3c.dependencychecker==2.10 -zpretty==2.4.1 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8849fbc..0000000 --- a/setup.cfg +++ /dev/null @@ -1,23 +0,0 @@ -# Generated from: -# https://github.com/plone/meta/tree/master/config/default -[bdist_wheel] -universal = 0 - -[flake8] -doctests = 1 -ignore = - # black takes care of line length - E501, - # black takes care of where to break lines - W503, - # black takes care of spaces within slicing (list[:]) - E203, - # black takes care of spaces after commas - E231, - -[check-manifest] -ignore = - .editorconfig - .meta.toml - tox.ini - lint-requirements.txt From c8d98b29aef49d2a6b0067108a22bddec974c2bb Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Sun, 25 Jun 2023 11:34:40 +0200 Subject: [PATCH 3/7] fix: adjust setup.py --- setup.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index d11973a..0cbf370 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +from pathlib import Path from setuptools import find_packages from setuptools import setup @@ -5,15 +6,17 @@ name = "borg.localrole" version = "3.1.11.dev0" -readme = open("README.rst").read() -history = open("CHANGES.rst").read() +long_description = ( + f"{Path('README.rst').read_text()}\n{Path('CHANGES.rst').read_text()}" +) setup( name=name, version=version, description="A PAS plugin which can manage local roles via an " "adapter lookup on the current context", - long_description=readme + "\n" + history, + long_description=long_description, + long_description_content_type="text/x-rst", keywords="Plone PAS local roles", author="Borg Collective", author_email="borg@plone.org", @@ -46,6 +49,8 @@ "Acquisition", "Zope", ], + # Get more strings from + # https://pypi.org/classifiers/ classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", @@ -59,9 +64,6 @@ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", # noqa "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", From 41e3f2c9e79787cd1b8270c0045addd9922f0f83 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Sun, 25 Jun 2023 11:36:01 +0200 Subject: [PATCH 4/7] chore: run black --- borg/localrole/setuphandlers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/borg/localrole/setuphandlers.py b/borg/localrole/setuphandlers.py index f30b14d..d6a9737 100644 --- a/borg/localrole/setuphandlers.py +++ b/borg/localrole/setuphandlers.py @@ -2,7 +2,6 @@ def importVarious(context): - if context.readDataFile("borg.localrole_various.txt") is None: return From 8061b03e72f2ef0a6f0ed0cae3cbad8ae3ed2e15 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Sun, 25 Jun 2023 11:36:44 +0200 Subject: [PATCH 5/7] chore: run zpretty --- .../zmi/WorkspaceLocalRoleManagerForm.pt | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt b/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt index d6609f5..1cbe588 100644 --- a/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt +++ b/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt @@ -1,28 +1,39 @@

Header

-

Form Title

+

Form Title

-

+

Install a Workspace local role manager. This plugin will enable workspaces to provide the correct local rules for users assigned to that workspace. -

+

-
+ - + - + @@ -30,4 +41,3 @@

Footer

- From 095bed44e929fe8d90700ea38b8573267bdce6e6 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Sun, 25 Jun 2023 11:37:46 +0200 Subject: [PATCH 6/7] chore: apply flake8 tips --- borg/localrole/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/borg/localrole/tests.py b/borg/localrole/tests.py index efaf773..853abb7 100644 --- a/borg/localrole/tests.py +++ b/borg/localrole/tests.py @@ -7,7 +7,6 @@ import borg.localrole import doctest -import re import unittest From 46874cba1acfaab025f577004557f6d913e1cb78 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Sun, 25 Jun 2023 11:42:04 +0200 Subject: [PATCH 7/7] fix: mark strings to not be translated --- borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt b/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt index 1cbe588..854e95c 100644 --- a/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt +++ b/borg/localrole/zmi/WorkspaceLocalRoleManagerForm.pt @@ -6,7 +6,9 @@ tal:replace="structure here/manage_form_title" >Form Title -

+

Install a Workspace local role manager. This plugin will enable workspaces to provide the correct local rules for users assigned to that workspace. @@ -17,13 +19,17 @@ >

Id
Title
- +
- + - + @@ -33,6 +39,7 @@
IdId
TitleTitle