diff --git a/docs/source/endpoints/userschema.md b/docs/source/endpoints/userschema.md index a59f582c92..4b15906920 100644 --- a/docs/source/endpoints/userschema.md +++ b/docs/source/endpoints/userschema.md @@ -37,3 +37,22 @@ The server will respond with the user schema. The user schema uses the same serialization as the type's JSON schema. See {ref}`types-schema` for detailed documentation about the available field types. + +## Getting the registration form + +In Plone we can configure each of the fields of the user schema to be available only in the user edit form, in the registration form or in both of them. + +To get the user schema available for the user registration form, make a request to the `/@registration-userschema` endpoint. + +```{eval-rst} +.. http:example:: curl httpie python-requests + :request: ../../../src/plone/restapi/tests/http-examples/registration_userschema.req +``` + +The server will respond with the user schema. + +```{literalinclude} ../../../src/plone/restapi/tests/http-examples/registration_userschema.resp + :language: http +``` + +The user schema uses the same serialization as the type's JSON schema. diff --git a/news/1873.feature b/news/1873.feature new file mode 100644 index 0000000000..e5ddb69202 --- /dev/null +++ b/news/1873.feature @@ -0,0 +1,2 @@ +Add a @registration-userschema endpoint to get the fields for the registration form +[erral] diff --git a/src/plone/restapi/services/userschema/configure.zcml b/src/plone/restapi/services/userschema/configure.zcml index ce25cc016a..30db69e43f 100644 --- a/src/plone/restapi/services/userschema/configure.zcml +++ b/src/plone/restapi/services/userschema/configure.zcml @@ -12,4 +12,5 @@ name="@userschema" /> + diff --git a/src/plone/restapi/services/userschema/user.py b/src/plone/restapi/services/userschema/user.py index 17e201bcca..89688de421 100644 --- a/src/plone/restapi/services/userschema/user.py +++ b/src/plone/restapi/services/userschema/user.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from plone.app.users.browser.register import getRegisterSchema from plone.app.users.browser.userdatapanel import getUserDataSchema from plone.restapi.serializer.converters import json_compatible from plone.restapi.services import Service @@ -6,11 +7,23 @@ from plone.restapi.types.utils import get_fieldsets from plone.restapi.types.utils import get_jsonschema_properties from plone.restapi.types.utils import iter_fields +from zope.interface import implementer +from zope.publisher.interfaces import IPublishTraverse +@implementer(IPublishTraverse) class UserSchemaGet(Service): - def reply(self): - user_schema = getUserDataSchema() + def __init__(self, context, request): + super().__init__(context, request) + self.params = [] + + def publishTraverse(self, request, name): + # Consume any path segments after /@userschema as parameters + self.params.append(name) + return self + + def build_userschema_as_jsonschema(self, user_schema): + """function to build a jsonschema from user schema information""" fieldsets = get_fieldsets(self.context, self.request, user_schema) # Build JSON schema properties @@ -33,3 +46,17 @@ def reply(self): "required": required, "fieldsets": get_fieldset_infos(fieldsets), } + + def reply(self): + if len(self.params) == 0: + return self.build_userschema_as_jsonschema(getUserDataSchema()) + elif len(self.params) == 1 and self.params[0] == "registration": + return self.build_userschema_as_jsonschema(getRegisterSchema()) + + self.request.response.setStatus(400) + return dict( + error=dict( + type="Invalid parameters", + message="Parameters supplied are not valid.", + ) + ) diff --git a/src/plone/restapi/tests/http-examples/registration_userschema.req b/src/plone/restapi/tests/http-examples/registration_userschema.req new file mode 100644 index 0000000000..2a6eeaa0d8 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/registration_userschema.req @@ -0,0 +1,3 @@ +GET /plone/@userschema/registration HTTP/1.1 +Accept: application/json +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/registration_userschema.resp b/src/plone/restapi/tests/http-examples/registration_userschema.resp new file mode 100644 index 0000000000..17970c2f7a --- /dev/null +++ b/src/plone/restapi/tests/http-examples/registration_userschema.resp @@ -0,0 +1,69 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "fieldsets": [ + { + "behavior": "plone", + "fields": [ + "fullname", + "email", + "username", + "password", + "password_ctl", + "mail_me" + ], + "id": "default", + "title": "Default" + } + ], + "properties": { + "email": { + "description": "We will use this address if you need to recover your password", + "factory": "Email", + "title": "Email", + "type": "string", + "widget": "email" + }, + "fullname": { + "description": "Enter full name, e.g. John Smith.", + "factory": "Text line (String)", + "title": "Full Name", + "type": "string" + }, + "mail_me": { + "default": false, + "description": "", + "factory": "Yes/No", + "title": "Send a confirmation mail with a link to set the password", + "type": "boolean" + }, + "password": { + "description": "Enter your new password.", + "factory": "Password", + "title": "Password", + "type": "string", + "widget": "password" + }, + "password_ctl": { + "description": "Re-enter the password. Make sure the passwords are identical.", + "factory": "Password", + "title": "Confirm password", + "type": "string", + "widget": "password" + }, + "username": { + "description": "Enter a user name, usually something like 'jsmith'. No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in.", + "factory": "Text line (String)", + "title": "User Name", + "type": "string" + } + }, + "required": [ + "email", + "username", + "password", + "password_ctl" + ], + "type": "object" +} diff --git a/src/plone/restapi/tests/test_documentation.py b/src/plone/restapi/tests/test_documentation.py index 5f2c92d97d..6a20fac0cb 100644 --- a/src/plone/restapi/tests/test_documentation.py +++ b/src/plone/restapi/tests/test_documentation.py @@ -2516,6 +2516,11 @@ def test_documentation_schema_user(self): save_request_and_response_for_docs("userschema", response) + def test_documentation_registration_schema_user(self): + response = self.api_session.get("/@userschema/registration") + + save_request_and_response_for_docs("registration_userschema", response) + class TestRules(TestDocumentationBase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING diff --git a/src/plone/restapi/tests/test_services_userschema.py b/src/plone/restapi/tests/test_services_userschema.py index 5a09bb1e48..327812e1a8 100644 --- a/src/plone/restapi/tests/test_services_userschema.py +++ b/src/plone/restapi/tests/test_services_userschema.py @@ -63,6 +63,38 @@ def test_userschema_get(self): self.assertTrue("object", response["type"]) + def test_registration_userschema_get(self): + response = self.api_session.get("/@userschema/registration") + + self.assertEqual(200, response.status_code) + response = response.json() + + self.assertIn("fullname", response["fieldsets"][0]["fields"]) + self.assertIn("email", response["fieldsets"][0]["fields"]) + self.assertIn("password", response["fieldsets"][0]["fields"]) + self.assertIn("password_ctl", response["fieldsets"][0]["fields"]) + self.assertIn("username", response["fieldsets"][0]["fields"]) + self.assertIn("mail_me", response["fieldsets"][0]["fields"]) + + self.assertIn("fullname", response["properties"]) + self.assertIn("email", response["properties"]) + self.assertIn("password", response["properties"]) + self.assertIn("password_ctl", response["properties"]) + self.assertIn("username", response["properties"]) + self.assertIn("mail_me", response["properties"]) + + self.assertIn("email", response["required"]) + self.assertIn("username", response["required"]) + self.assertIn("password", response["required"]) + self.assertIn("password_ctl", response["required"]) + + self.assertTrue("object", response["type"]) + + def test_userschema_with_invalid_params(self): + response = self.api_session.get("/@userschema/something-invalid") + + self.assertEqual(400, response.status_code) + @unittest.skipIf(not PLONE5, "Just Plone 5 currently.") class TestCustomUserSchema(unittest.TestCase): @@ -133,7 +165,7 @@ def setUp(self): False Age - + False Department @@ -159,7 +191,7 @@ def setUp(self): False Pi - + False Vegetarian @@ -196,3 +228,31 @@ def test_userschema_get(self): self.assertIn("skills", response["fieldsets"][0]["fields"]) self.assertIn("pi", response["fieldsets"][0]["fields"]) self.assertIn("vegetarian", response["fieldsets"][0]["fields"]) + + def test_userschema_for_registration_get(self): + response = self.api_session.get("/@userschema/registration") + + self.assertEqual(200, response.status_code) + response = response.json() + # Default fields + self.assertIn("fullname", response["fieldsets"][0]["fields"]) + self.assertIn("email", response["fieldsets"][0]["fields"]) + self.assertIn("username", response["fieldsets"][0]["fields"]) + self.assertIn("password", response["fieldsets"][0]["fields"]) + self.assertIn("password_ctl", response["fieldsets"][0]["fields"]) + self.assertIn("mail_me", response["fieldsets"][0]["fields"]) + + # added fields + self.assertIn("department", response["fieldsets"][0]["fields"]) + self.assertIn("vegetarian", response["fieldsets"][0]["fields"]) + + # fields not shown in the regisration form + self.assertNotIn("home_page", response["fieldsets"][0]["fields"]) + self.assertNotIn("description", response["fieldsets"][0]["fields"]) + self.assertNotIn("location", response["fieldsets"][0]["fields"]) + self.assertNotIn("portrait", response["fieldsets"][0]["fields"]) + self.assertNotIn("birthdate", response["fieldsets"][0]["fields"]) + self.assertNotIn("another_date", response["fieldsets"][0]["fields"]) + self.assertNotIn("age", response["fieldsets"][0]["fields"]) + self.assertNotIn("skills", response["fieldsets"][0]["fields"]) + self.assertNotIn("pi", response["fieldsets"][0]["fields"])