Skip to content

Commit 8f67e38

Browse files
sampaccoudAntoLC
authored andcommitted
♻️(backend) refactor get_select_options to take definitions dict
This will allow us to simplify the get_abilities method. It is also more efficient because we have computed this definitions dict and the the get_select_options method was doing the conversion again.
1 parent 18d46ac commit 8f67e38

File tree

4 files changed

+73
-126
lines changed

4 files changed

+73
-126
lines changed

src/backend/core/choices.py

Lines changed: 32 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -61,63 +61,52 @@ class LinkReachChoices(PriorityTextChoices):
6161
) # Any authenticated user can access the document
6262
PUBLIC = "public", _("Public") # Even anonymous users can access the document
6363

64+
6465
@classmethod
65-
def get_select_options(cls, ancestors_links):
66+
def get_select_options(cls, link_reach, link_role):
6667
"""
6768
Determines the valid select options for link reach and link role depending on the
68-
list of ancestors' link reach/role.
69-
Args:
70-
ancestors_links: List of dictionaries, each with 'link_reach' and 'link_role' keys
71-
representing the reach and role of ancestors links.
69+
list of ancestors' link reach/role definitions.
7270
Returns:
7371
Dictionary mapping possible reach levels to their corresponding possible roles.
7472
"""
7573
# If no ancestors, return all options
76-
if not ancestors_links:
74+
if not link_reach:
7775
return {
7876
reach: LinkRoleChoices.values if reach != cls.RESTRICTED else None
7977
for reach in cls.values
8078
}
8179

82-
# Initialize result with all possible reaches and role options as sets
80+
# Initialize the result for all reaches with possible roles
8381
result = {
8482
reach: set(LinkRoleChoices.values) if reach != cls.RESTRICTED else None
8583
for reach in cls.values
8684
}
8785

88-
# Group roles by reach level
89-
reach_roles = defaultdict(set)
90-
for link in ancestors_links:
91-
reach_roles[link["link_reach"]].add(link["link_role"])
92-
93-
# Rule 1: public/editor → override everything
94-
if LinkRoleChoices.EDITOR in reach_roles.get(cls.PUBLIC, set()):
95-
return {cls.PUBLIC: [LinkRoleChoices.EDITOR]}
96-
97-
# Rule 2: authenticated/editor
98-
if LinkRoleChoices.EDITOR in reach_roles.get(cls.AUTHENTICATED, set()):
99-
result[cls.AUTHENTICATED].discard(LinkRoleChoices.READER)
100-
result.pop(cls.RESTRICTED, None)
101-
102-
# Rule 3: public/reader
103-
if LinkRoleChoices.READER in reach_roles.get(cls.PUBLIC, set()):
104-
result.pop(cls.AUTHENTICATED, None)
105-
result.pop(cls.RESTRICTED, None)
106-
107-
# Rule 4: authenticated/reader
108-
if LinkRoleChoices.READER in reach_roles.get(cls.AUTHENTICATED, set()):
109-
result.pop(cls.RESTRICTED, None)
110-
111-
# Clean up: remove empty entries and convert sets to ordered lists
112-
cleaned = {}
113-
for reach in cls.values:
114-
if reach in result:
115-
if result[reach]:
116-
cleaned[reach] = [
117-
r for r in LinkRoleChoices.values if r in result[reach]
118-
]
119-
else:
120-
# Could be [] or None (for RESTRICTED reach)
121-
cleaned[reach] = result[reach]
122-
123-
return cleaned
86+
# Handle special rules directly with early returns for efficiency
87+
88+
if link_role == LinkRoleChoices.EDITOR:
89+
# Rule 1: public/editor → override everything
90+
if link_reach == cls.PUBLIC:
91+
return {cls.PUBLIC: [LinkRoleChoices.EDITOR]}
92+
93+
# Rule 2: authenticated/editor
94+
if link_reach == cls.AUTHENTICATED:
95+
result[cls.AUTHENTICATED].discard(LinkRoleChoices.READER)
96+
result.pop(cls.RESTRICTED, None)
97+
98+
if link_role == LinkRoleChoices.READER:
99+
# Rule 3: public/reader
100+
if link_reach == cls.PUBLIC:
101+
result.pop(cls.AUTHENTICATED, None)
102+
result.pop(cls.RESTRICTED, None)
103+
104+
# Rule 4: authenticated/reader
105+
if link_reach == cls.AUTHENTICATED:
106+
result.pop(cls.RESTRICTED, None)
107+
108+
# Convert sets to ordered lists where applicable
109+
return {
110+
reach: sorted(roles, key=LinkRoleChoices.get_priority) if roles else roles
111+
for reach, roles in result.items()
112+
}

src/backend/core/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,9 @@ def get_abilities(self, user, ancestors_links=None):
819819
"ancestors_links_definitions": {
820820
k: list(v) for k, v in ancestors_links_definitions.items()
821821
},
822-
"link_select_options": LinkReachChoices.get_select_options(ancestors_links),
822+
"link_select_options": LinkReachChoices.get_select_options(
823+
ancestors_links_definitions
824+
),
823825
"tree": can_get,
824826
"update": can_update,
825827
"versions_destroy": is_owner_or_admin,

src/backend/core/tests/documents/test_api_documents_retrieve.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Tests for Documents API endpoint in impress's core app: retrieve
33
"""
4+
# pylint: disable=too-many-lines
45

56
import random
67
from datetime import timedelta
@@ -93,6 +94,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
9394

9495
assert response.status_code == 200
9596
links = document.get_ancestors().values("link_reach", "link_role")
97+
links_definitions = document.get_ancestors_links_definitions(links)
9698
assert response.json() == {
9799
"id": str(document.id),
98100
"abilities": {
@@ -117,7 +119,9 @@ def test_api_documents_retrieve_anonymous_public_parent():
117119
"favorite": False,
118120
"invite_owner": False,
119121
"link_configuration": False,
120-
"link_select_options": models.LinkReachChoices.get_select_options(links),
122+
"link_select_options": models.LinkReachChoices.get_select_options(
123+
links_definitions
124+
),
121125
"media_auth": True,
122126
"media_check": True,
123127
"move": False,
@@ -272,6 +276,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
272276

273277
assert response.status_code == 200
274278
links = document.get_ancestors().values("link_reach", "link_role")
279+
links_definitions = document.get_ancestors_links_definitions(links)
275280
assert response.json() == {
276281
"id": str(document.id),
277282
"abilities": {
@@ -295,10 +300,12 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
295300
"favorite": True,
296301
"invite_owner": False,
297302
"link_configuration": False,
298-
"link_select_options": models.LinkReachChoices.get_select_options(links),
303+
"link_select_options": models.LinkReachChoices.get_select_options(
304+
links_definitions
305+
),
306+
"move": False,
299307
"media_auth": True,
300308
"media_check": True,
301-
"move": False,
302309
"partial_update": grand_parent.link_role == "editor",
303310
"restore": False,
304311
"retrieve": True,
@@ -458,6 +465,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
458465
)
459466
assert response.status_code == 200
460467
links = document.get_ancestors().values("link_reach", "link_role")
468+
links_definitions = document.get_ancestors_links_definitions(links)
461469
ancestors_roles = list({grand_parent.link_role, parent.link_role})
462470
assert response.json() == {
463471
"id": str(document.id),
@@ -479,7 +487,9 @@ def test_api_documents_retrieve_authenticated_related_parent():
479487
"favorite": True,
480488
"invite_owner": access.role == "owner",
481489
"link_configuration": access.role in ["administrator", "owner"],
482-
"link_select_options": models.LinkReachChoices.get_select_options(links),
490+
"link_select_options": models.LinkReachChoices.get_select_options(
491+
links_definitions
492+
),
483493
"media_auth": True,
484494
"media_check": True,
485495
"move": access.role in ["administrator", "owner"],

src/backend/core/tests/test_models_documents.py

Lines changed: 24 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,124 +1192,70 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
11921192

11931193

11941194
@pytest.mark.parametrize(
1195-
"ancestors_links, select_options",
1195+
"reach, role, select_options",
11961196
[
11971197
# One ancestor
11981198
(
1199-
[{"link_reach": "public", "link_role": "reader"}],
1199+
"public",
1200+
"reader",
12001201
{
12011202
"public": ["reader", "editor"],
12021203
},
12031204
),
1204-
([{"link_reach": "public", "link_role": "editor"}], {"public": ["editor"]}),
1205+
("public", "editor", {"public": ["editor"]}),
12051206
(
1206-
[{"link_reach": "authenticated", "link_role": "reader"}],
1207+
"authenticated",
1208+
"reader",
12071209
{
12081210
"authenticated": ["reader", "editor"],
12091211
"public": ["reader", "editor"],
12101212
},
12111213
),
12121214
(
1213-
[{"link_reach": "authenticated", "link_role": "editor"}],
1215+
"authenticated",
1216+
"editor",
12141217
{"authenticated": ["editor"], "public": ["reader", "editor"]},
12151218
),
12161219
(
1217-
[{"link_reach": "restricted", "link_role": "reader"}],
1220+
"restricted",
1221+
"reader",
12181222
{
12191223
"restricted": None,
12201224
"authenticated": ["reader", "editor"],
12211225
"public": ["reader", "editor"],
12221226
},
12231227
),
12241228
(
1225-
[{"link_reach": "restricted", "link_role": "editor"}],
1229+
"restricted",
1230+
"editor",
12261231
{
12271232
"restricted": None,
12281233
"authenticated": ["reader", "editor"],
12291234
"public": ["reader", "editor"],
12301235
},
12311236
),
1232-
# Multiple ancestors with different roles
1233-
(
1234-
[
1235-
{"link_reach": "public", "link_role": "reader"},
1236-
{"link_reach": "public", "link_role": "editor"},
1237-
],
1238-
{"public": ["editor"]},
1239-
),
1240-
(
1241-
[
1242-
{"link_reach": "authenticated", "link_role": "reader"},
1243-
{"link_reach": "authenticated", "link_role": "editor"},
1244-
],
1245-
{"authenticated": ["editor"], "public": ["reader", "editor"]},
1246-
),
1247-
(
1248-
[
1249-
{"link_reach": "restricted", "link_role": "reader"},
1250-
{"link_reach": "restricted", "link_role": "editor"},
1251-
],
1252-
{
1253-
"restricted": None,
1254-
"authenticated": ["reader", "editor"],
1255-
"public": ["reader", "editor"],
1256-
},
1257-
),
1258-
# Multiple ancestors with different reaches
1237+
# No ancestors (edge case)
12591238
(
1260-
[
1261-
{"link_reach": "authenticated", "link_role": "reader"},
1262-
{"link_reach": "public", "link_role": "reader"},
1263-
],
1239+
"public",
1240+
None,
12641241
{
12651242
"public": ["reader", "editor"],
1243+
"authenticated": ["reader", "editor"],
1244+
"restricted": None,
12661245
},
12671246
),
12681247
(
1269-
[
1270-
{"link_reach": "restricted", "link_role": "reader"},
1271-
{"link_reach": "authenticated", "link_role": "reader"},
1272-
{"link_reach": "public", "link_role": "reader"},
1273-
],
1248+
None,
1249+
"reader",
12741250
{
12751251
"public": ["reader", "editor"],
1276-
},
1277-
),
1278-
# Multiple ancestors with mixed reaches and roles
1279-
(
1280-
[
1281-
{"link_reach": "authenticated", "link_role": "editor"},
1282-
{"link_reach": "public", "link_role": "reader"},
1283-
],
1284-
{"public": ["reader", "editor"]},
1285-
),
1286-
(
1287-
[
1288-
{"link_reach": "authenticated", "link_role": "reader"},
1289-
{"link_reach": "public", "link_role": "editor"},
1290-
],
1291-
{"public": ["editor"]},
1292-
),
1293-
(
1294-
[
1295-
{"link_reach": "restricted", "link_role": "editor"},
1296-
{"link_reach": "authenticated", "link_role": "reader"},
1297-
],
1298-
{
12991252
"authenticated": ["reader", "editor"],
1300-
"public": ["reader", "editor"],
1253+
"restricted": None,
13011254
},
13021255
),
13031256
(
1304-
[
1305-
{"link_reach": "restricted", "link_role": "reader"},
1306-
{"link_reach": "authenticated", "link_role": "editor"},
1307-
],
1308-
{"authenticated": ["editor"], "public": ["reader", "editor"]},
1309-
),
1310-
# No ancestors (edge case)
1311-
(
1312-
[],
1257+
None,
1258+
None,
13131259
{
13141260
"public": ["reader", "editor"],
13151261
"authenticated": ["reader", "editor"],
@@ -1318,9 +1264,9 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
13181264
),
13191265
],
13201266
)
1321-
def test_models_documents_get_select_options(ancestors_links, select_options):
1267+
def test_models_documents_get_select_options(reach, role, select_options):
13221268
"""Validate that the "get_select_options" method operates as expected."""
1323-
assert models.LinkReachChoices.get_select_options(ancestors_links) == select_options
1269+
assert models.LinkReachChoices.get_select_options(reach, role) == select_options
13241270

13251271

13261272
def test_models_documents_compute_ancestors_links_no_highest_readable():

0 commit comments

Comments
 (0)