Skip to content

Commit df9c485

Browse files
authored
Merge pull request #2148 from pbiering/sharing-bday-template-api
Sharing bday template api regression
2 parents cbafdc3 + 2f3b6ec commit df9c485

3 files changed

Lines changed: 215 additions & 2 deletions

File tree

SHARING.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,8 @@ Execute delete+create in case `PathOrToken` needs to be changed.
526526
527527
* Output: text/plain|application/json
528528
529+
* Level-2 entries in "Actions" can be deleted using special value `#DEL#` (see API example below related to "bday" conversion)
530+
529531
* Examples:
530532
531533
###### form->text
@@ -738,3 +740,79 @@ BEGIN:VCALENDAR
738740
...
739741
END:VCALENDAR
740742
```
743+
744+
##### Adjusting templates per share
745+
746+
```bash
747+
## set specific config: conversion_bday_summary_template
748+
curl -u owner:$ownerpw -H "Content-Type: application/json" -d '{ "PathMapped": "/owner/addressbook/", "PathOrToken": "/owner/bday-of-addressbook/", "Actions": { "config": { "conversion_bday_summary_template": "{fn} ({year}" }}}' http://localhost:5232/.sharing/v1/map/update
749+
{"ApiVersion": 1, "Status": "success"}
750+
```
751+
752+
```bash
753+
## list adjusted share
754+
curl -s -H "Content-Type: application/json" -u owner:$ownerpw -d '{ "PathMapped": "/owner/addressbook/", "PathOrToken": "/owner/bday-of-addressbook/"}' http://localhost:5232/.sharing/v1/map/list | jq
755+
{
756+
"ApiVersion": 1,
757+
"Lines": 1,
758+
"Status": "success",
759+
"Content": [
760+
{
761+
"ShareType": "map",
762+
"PathOrToken": "/owner/bday-of-addressbook/",
763+
"PathMapped": "/owner/addressbook/",
764+
"Conversion": "bday",
765+
"Owner": "owner",
766+
"User": "owner",
767+
"Permissions": "r",
768+
"EnabledByOwner": true,
769+
"EnabledByUser": true,
770+
"HiddenByOwner": false,
771+
"HiddenByUser": false,
772+
"TimestampCreated": 1774339430,
773+
"TimestampUpdated": 1780240328,
774+
"Properties": {},
775+
"Actions": {
776+
"config": {
777+
"conversion_bday_summary_template": "{fn} ({year}"
778+
}
779+
}
780+
}
781+
]
782+
}
783+
```
784+
785+
```bash
786+
## delete specific config: conversion_bday_summary_template (using special value "#DEL#")
787+
curl -u owner:$ownerpw -H "Content-Type: application/json" -d '{ "PathMapped": "/owner/addressbook/", "PathOrToken": "/owner/bday-of-addressbook/", "Actions": { "config": { "conversion_bday_summary_template": "#DEL#" }}}' http://localhost:5232/.sharing/v1/map/update
788+
{"ApiVersion": 1, "Status": "success"}
789+
```
790+
791+
```bash
792+
## list share (specific config is deleted)
793+
curl -s -H "Content-Type: application/json" -u owner:$ownerpw -d '{ "PathMapped": "/owner/addressbook/", "PathOrToken": "/owner/bday-of-addressbook/"}' http://localhost:5232/.sharing/v1/map/list | jq
794+
{
795+
"ApiVersion": 1,
796+
"Lines": 1,
797+
"Status": "success",
798+
"Content": [
799+
{
800+
"ShareType": "map",
801+
"PathOrToken": "/owner/bday-of-addressbook/",
802+
"PathMapped": "/owner/addressbook/",
803+
"Conversion": "bday",
804+
"Owner": "owner",
805+
"User": "owner",
806+
"Permissions": "r",
807+
"EnabledByOwner": true,
808+
"EnabledByUser": true,
809+
"HiddenByOwner": false,
810+
"HiddenByUser": false,
811+
"TimestampCreated": 1774339430,
812+
"TimestampUpdated": 1780240558,
813+
"Properties": {},
814+
"Actions": {}
815+
}
816+
]
817+
}
818+
```

radicale/sharing/__init__.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
SHARING_BDAY_SUMMARY_TEMPLATE_DEFAULT: str = "[{n:f} {n:g}|{fn}|{nickname}] ({year}) (BDAY)"
129129
SHARING_BDAY_DESCRIPTION_TEMPLATE_DEFAULT: str = "BDAY={year}-{month}-{day}"
130130
SHARING_BDAY_CATEGORIES_DEFAULT: str = 'Birthday'
131+
SHARING_ACTIONS_DELETE_VALUE: str = '#DEL#'
131132

132133

133134
def check_bday_max_age(data: Any) -> int:
@@ -555,7 +556,6 @@ def sharing_collection_resolver(self, path: str, user: str) -> Union[dict, None]
555556

556557
# adjust a share
557558
def sharing_collection_update(self, ShareType: str, PathOrToken: str, OwnerOrUser: str, Properties: dict) -> None:
558-
""" returning dict with PathMapped, Owner, Permissions or None if not found"""
559559
logger.info("sharing/collection/update: ShareType=%r PathOrToken=%r OwnerOrUser=%r", ShareType, PathOrToken, OwnerOrUser)
560560
# Filter properies for permitted ones
561561
properties_filtered: dict = {}
@@ -943,7 +943,7 @@ def post(self, environ: types.WSGIEnviron, base_prefix: str, path: str, user: st
943943
if level1 in ACTIONS_WHITELIST:
944944
for level2 in request_data['Actions'][level1]:
945945
if level2 in ACTIONS_WHITELIST[level1]:
946-
if callable(ACTIONS_WHITELIST[level1][level2]):
946+
if callable(ACTIONS_WHITELIST[level1][level2]) and request_data['Actions'][level1][level2] != SHARING_ACTIONS_DELETE_VALUE:
947947
try:
948948
value = ACTIONS_WHITELIST[level1][level2](request_data['Actions'][level1][level2])
949949
except ValueError:
@@ -1253,6 +1253,32 @@ def post(self, environ: types.WSGIEnviron, base_prefix: str, path: str, user: st
12531253
logger.trace("" + api_info + ": clear property %r", prop)
12541254
del Properties[prop]
12551255

1256+
if 'Actions' in request_data:
1257+
if Actions is None:
1258+
# clear actions
1259+
Actions = {}
1260+
elif Actions == {}:
1261+
# empty, nothing to do
1262+
pass
1263+
elif share['Actions'] is not None:
1264+
# replace properties
1265+
for level1 in share['Actions']:
1266+
if level1 not in Actions:
1267+
Actions[level1] = {} # initialize level1
1268+
for level2 in share['Actions'][level1]:
1269+
logger.trace("" + api_info + ": check for existing Actions entry %r->%r", level1, level2)
1270+
if level2 not in Actions[level1]:
1271+
logger.trace("" + api_info + ": overtake Actions entry %r->%r", level1, level2)
1272+
Actions[level1][level2] = share['Actions'][level1][level2]
1273+
elif Actions[level1][level2] == SHARING_ACTIONS_DELETE_VALUE:
1274+
# unset, do nothing
1275+
logger.trace("" + api_info + ": delete Actions entry %r->%r", level1, level2)
1276+
del Actions[level1][level2]
1277+
if len(Actions[level1]) == 0:
1278+
logger.trace("" + api_info + ": delete Actions entry %r", level1)
1279+
# unset level1
1280+
del Actions[level1]
1281+
12561282
if Permissions is not None and share['Conversion'] is not None:
12571283
Permissions = str(Permissions)
12581284
if share['Conversion'] == "bday":

radicale/tests/test_sharing.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5199,6 +5199,115 @@ def test_sharing_api_map_vcf_bday_template(self) -> None:
51995199
}}
52005200
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
52015201

5202+
self.configure({
5203+
"sharing": {"conversion_bday_description_template": sharing.SHARING_BDAY_DESCRIPTION_TEMPLATE_DEFAULT,
5204+
"conversion_bday_alarm_trigger_template": "",
5205+
}
5206+
})
5207+
5208+
# update template
5209+
logging.info("\n*** update map(bday) user/owner:r with valid description template -> 200")
5210+
json_dict = {}
5211+
json_dict['User'] = "user"
5212+
json_dict['PathMapped'] = path_mapped
5213+
json_dict['PathOrToken'] = path_shared_r
5214+
json_dict['Actions'] = {"config": {
5215+
"conversion_bday_summary_template": "{fn} ({year})",
5216+
"conversion_bday_description_template": "Birthday={year}-{month}-{day}"
5217+
}}
5218+
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
5219+
5220+
logging.info("\n*** GET collection user format: description -> ok")
5221+
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
5222+
assert "DESCRIPTION:Birthday=1990-01-01" in answer
5223+
assert "SUMMARY:Test-FN-C3 (1990)" in answer
5224+
5225+
# update template
5226+
logging.info("\n*** update map(bday) user/owner:r with valid empty description template -> 200")
5227+
json_dict = {}
5228+
json_dict['User'] = "user"
5229+
json_dict['PathMapped'] = path_mapped
5230+
json_dict['PathOrToken'] = path_shared_r
5231+
json_dict['Actions'] = {"config": {
5232+
"conversion_bday_description_template": ""
5233+
}}
5234+
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
5235+
5236+
logging.info("\n*** GET collection user format: description -> ok")
5237+
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
5238+
assert "DESCRIPTION:Birthday=" not in answer
5239+
assert "DESCRIPTION:BDAY=" not in answer
5240+
assert "SUMMARY:Test-FN-C3 (1990)" in answer
5241+
5242+
# update template
5243+
logging.info("\n*** update map(bday) user/owner:r DEL description template -> 200")
5244+
json_dict = {}
5245+
json_dict['User'] = "user"
5246+
json_dict['PathMapped'] = path_mapped
5247+
json_dict['PathOrToken'] = path_shared_r
5248+
json_dict['Actions'] = {"config": {
5249+
"conversion_bday_description_template": sharing.SHARING_ACTIONS_DELETE_VALUE
5250+
}}
5251+
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
5252+
5253+
logging.info("\n*** GET collection user format: description -> ok")
5254+
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
5255+
assert "DESCRIPTION:Birthday=" not in answer
5256+
assert "DESCRIPTION:BDAY=" in answer
5257+
assert "SUMMARY:Test-FN-C3 (1990)" in answer
5258+
5259+
# update template
5260+
logging.info("\n*** update map(bday) user/owner:r DEL summary template -> 200")
5261+
json_dict = {}
5262+
json_dict['User'] = "user"
5263+
json_dict['PathMapped'] = path_mapped
5264+
json_dict['PathOrToken'] = path_shared_r
5265+
json_dict['Actions'] = {"config": {
5266+
"conversion_bday_summary_template": sharing.SHARING_ACTIONS_DELETE_VALUE
5267+
}}
5268+
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
5269+
5270+
logging.info("\n*** GET collection user format: description -> ok")
5271+
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
5272+
assert "DESCRIPTION:Birthday=" not in answer
5273+
assert "DESCRIPTION:BDAY=" in answer
5274+
assert "SUMMARY:Family3Test Given3Test !n:a! (Birthday)" in answer
5275+
5276+
# update template
5277+
logging.info("\n*** update map(bday) user/owner:r with valid age max -> 200")
5278+
json_dict = {}
5279+
json_dict['User'] = "user"
5280+
json_dict['PathMapped'] = path_mapped
5281+
json_dict['PathOrToken'] = path_shared_r
5282+
json_dict['Actions'] = {"config": {
5283+
"conversion_bday_age_max": 5,
5284+
"conversion_bday_summary_template": "{fn} ({year}/{age})",
5285+
}}
5286+
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
5287+
5288+
logging.info("\n*** GET collection user format: summary with age -> ok")
5289+
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
5290+
assert "Test-FN-C3 (1990/0)" in answer
5291+
assert "Test-FN-C3 (1990/5)" in answer
5292+
assert "Test-FN-C3 (1990/6)" not in answer
5293+
5294+
# update template
5295+
logging.info("\n*** update map(bday) user/owner:r with valid age max -> 200")
5296+
json_dict = {}
5297+
json_dict['User'] = "user"
5298+
json_dict['PathMapped'] = path_mapped
5299+
json_dict['PathOrToken'] = path_shared_r
5300+
json_dict['Actions'] = {"config": {
5301+
"conversion_bday_age_max": sharing.SHARING_ACTIONS_DELETE_VALUE
5302+
}}
5303+
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
5304+
5305+
logging.info("\n*** GET collection user format: summary with age -> ok")
5306+
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
5307+
assert "Test-FN-C3 (1990/0)" in answer
5308+
assert "Test-FN-C3 (1990/5)" in answer
5309+
assert "Test-FN-C3 (1990/6)" in answer
5310+
52025311
def test_sharing_api_map_vcf_bday_age_template(self) -> None:
52035312
"""share-by-map with conversion=bday template tests with age."""
52045313
self.configure({"auth": {"type": "htpasswd",

0 commit comments

Comments
 (0)