diff --git a/news/3953.breaking.2 b/news/3953.breaking.2
new file mode 100644
index 00000000..7cccb4ea
--- /dev/null
+++ b/news/3953.breaking.2
@@ -0,0 +1,2 @@
+Move ``plone.app.users`` template/views to ``plone.app.layout.views.users``
+[tlotze]
diff --git a/setup.py b/setup.py
index 21ced550..3f0893f6 100644
--- a/setup.py
+++ b/setup.py
@@ -47,6 +47,7 @@
"plone.app.registry",
"plone.app.relationfield",
"plone.app.workflow",
+ "plone.app.users",
"plone.app.uuid",
"plone.app.viewletmanager >=1.2",
"plone.autoform",
@@ -57,6 +58,9 @@
"plone.memoize",
"plone.portlets",
"plone.protect",
+ "plone.schemaeditor",
+ "plone.supermodel",
+ "plone.z3cform",
"Products.CMFEditions >=1.2.2",
"Products.GenericSetup",
"Products.statusmessages",
diff --git a/src/plone/app/layout/tests/test_member_search.py b/src/plone/app/layout/tests/test_member_search.py
new file mode 100644
index 00000000..7b1a5a49
--- /dev/null
+++ b/src/plone/app/layout/tests/test_member_search.py
@@ -0,0 +1,17 @@
+from plone.app.layout.users.membersearch import extractCriteriaFromRequest
+
+import unittest
+
+
+class TestMemberSearch(unittest.TestCase):
+ def test_extract_criteria_from_request(self):
+ data = {
+ "_authenticator": "ab4731...",
+ "form.buttons.search": "Search",
+ "form.widgets.something": "any form value",
+ "form.widgets.roles-empty-marker": True,
+ }
+
+ result = extractCriteriaFromRequest(data)
+
+ self.assertEqual(result, {"something": "any form value"})
diff --git a/src/plone/app/layout/tests/test_user_data_panel.py b/src/plone/app/layout/tests/test_user_data_panel.py
new file mode 100644
index 00000000..782e8fbd
--- /dev/null
+++ b/src/plone/app/layout/tests/test_user_data_panel.py
@@ -0,0 +1,25 @@
+from plone.app.layout.users.userdatapanel import UserDataPanel
+from plone.app.users.tests.base import BaseTestCase
+from zExceptions import NotFound
+from zope.i18n import translate
+
+
+class TestUserDataPanel(BaseTestCase):
+ def test_regression(self):
+ portal = self.layer["portal"]
+ request = self.layer["request"]
+ request.form.update({"userid": "admin"})
+ form = UserDataPanel(portal, request)
+ description = translate(form.description, context=request)
+ self.assertTrue("admin" in description)
+ # form can be called without raising exception.
+ self.assertTrue(form())
+
+ def test_escape_html(self):
+ portal = self.layer["portal"]
+ request = self.layer["request"]
+ request.form.update({"userid": 'admin'})
+ form = UserDataPanel(portal, request)
+ description = translate(form.description, context=request)
+ self.assertTrue("