diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 2b30ed72..a3e88692 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -870,6 +870,26 @@ Command that is run after changes to storage. Take a look at the Default: +##### predefined_collections + +Create predefined user collections + + Example: + + { + "def-addressbook": { + "D:displayname": "Personal Address Book", + "tag": "VADDRESSBOOK" + }, + "def-calendar": { + "C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO", + "D:displayname": "Personal Calendar", + "tag": "VCALENDAR" + } + } + +Default: + #### web ##### type diff --git a/config b/config index 4cab70c6..c2e02607 100644 --- a/config +++ b/config @@ -103,6 +103,24 @@ # Example: ([ -d .git ] || git init) && git add -A && (git diff --cached --quiet || git commit -m "Changes by \"%(user)s\"") #hook = +# Create predefined user collections +# +# json format: +# +# { +# "def-addressbook": { +# "D:displayname": "Personal Address Book", +# "tag": "VADDRESSBOOK" +# }, +# "def-calendar": { +# "C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO", +# "D:displayname": "Personal Calendar", +# "tag": "VCALENDAR" +# } +# } +# +#predefined_collections = + [web] diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index 37ab6a5e..40809e2d 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -48,6 +48,7 @@ from radicale.app.propfind import ApplicationPartPropfind from radicale.app.proppatch import ApplicationPartProppatch from radicale.app.put import ApplicationPartPut from radicale.app.report import ApplicationPartReport +from radicale.item import check_and_sanitize_props from radicale.log import logger # Combination of types.WSGIStartResponse and WSGI application return value @@ -268,7 +269,15 @@ class Application(ApplicationPartDelete, ApplicationPartHead, if "W" in self._rights.authorization(user, principal_path): with self._storage.acquire_lock("w", user): try: - self._storage.create_collection(principal_path) + new_coll = self._storage.create_collection(principal_path) + if new_coll: + jsn_coll = self.configuration.get("storage", "predefined_collections") + for (name_coll, props) in jsn_coll.items(): + try: + checked_props = check_and_sanitize_props(props) + self._storage.create_collection(principal_path + name_coll, props=checked_props) + except ValueError as e: + logger.warning("Failed to create predefined collection %r: %s", name_coll, e) except ValueError as e: logger.warning("Failed to create principal " "collection %r: %s", user, e) diff --git a/radicale/config.py b/radicale/config.py index b1aa9bf6..d2e66835 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -26,6 +26,7 @@ Use ``load()`` to obtain an instance of ``Configuration`` for use with """ import contextlib +import json import math import os import string @@ -101,6 +102,12 @@ def _convert_to_bool(value: Any) -> bool: return RawConfigParser.BOOLEAN_STATES[value.lower()] +def json_str(value: Any) -> dict: + if not value: + return {} + return json.loads(value) + + INTERNAL_OPTIONS: Sequence[str] = ("_allow_extra",) # Default configuration DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ @@ -217,7 +224,11 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ ("_filesystem_fsync", { "value": "True", "help": "sync all changes to filesystem during requests", - "type": bool})])), + "type": bool}), + ("predefined_collections", { + "value": "", + "help": "predefined user collections", + "type": json_str})])), ("hook", OrderedDict([ ("type", { "value": "none",