Skip to content

Commit 6edb92e

Browse files
sampaccoudPanchoutNathan
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 ee8f61a commit 6edb92e

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
@@ -818,7 +818,9 @@ def get_abilities(self, user, ancestors_links=None):
818818
"ancestors_links_definitions": {
819819
k: list(v) for k, v in ancestors_links_definitions.items()
820820
},
821-
"link_select_options": LinkReachChoices.get_select_options(ancestors_links),
821+
"link_select_options": LinkReachChoices.get_select_options(
822+
ancestors_links_definitions
823+
),
822824
"tree": can_get,
823825
"update": can_update,
824826
"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
@@ -92,6 +93,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
9293

9394
assert response.status_code == 200
9495
links = document.get_ancestors().values("link_reach", "link_role")
96+
links_definitions = document.get_ancestors_links_definitions(links)
9597
assert response.json() == {
9698
"id": str(document.id),
9799
"abilities": {
@@ -115,7 +117,9 @@ def test_api_documents_retrieve_anonymous_public_parent():
115117
"favorite": False,
116118
"invite_owner": False,
117119
"link_configuration": False,
118-
"link_select_options": models.LinkReachChoices.get_select_options(links),
120+
"link_select_options": models.LinkReachChoices.get_select_options(
121+
links_definitions
122+
),
119123
"media_auth": True,
120124
"media_check": True,
121125
"move": False,
@@ -269,6 +273,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
269273

270274
assert response.status_code == 200
271275
links = document.get_ancestors().values("link_reach", "link_role")
276+
links_definitions = document.get_ancestors_links_definitions(links)
272277
assert response.json() == {
273278
"id": str(document.id),
274279
"abilities": {
@@ -291,10 +296,12 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
291296
"favorite": True,
292297
"invite_owner": False,
293298
"link_configuration": False,
294-
"link_select_options": models.LinkReachChoices.get_select_options(links),
299+
"link_select_options": models.LinkReachChoices.get_select_options(
300+
links_definitions
301+
),
302+
"move": False,
295303
"media_auth": True,
296304
"media_check": True,
297-
"move": False,
298305
"partial_update": grand_parent.link_role == "editor",
299306
"restore": False,
300307
"retrieve": True,
@@ -454,6 +461,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
454461
)
455462
assert response.status_code == 200
456463
links = document.get_ancestors().values("link_reach", "link_role")
464+
links_definitions = document.get_ancestors_links_definitions(links)
457465
ancestors_roles = list({grand_parent.link_role, parent.link_role})
458466
assert response.json() == {
459467
"id": str(document.id),
@@ -474,7 +482,9 @@ def test_api_documents_retrieve_authenticated_related_parent():
474482
"favorite": True,
475483
"invite_owner": access.role == "owner",
476484
"link_configuration": access.role in ["administrator", "owner"],
477-
"link_select_options": models.LinkReachChoices.get_select_options(links),
485+
"link_select_options": models.LinkReachChoices.get_select_options(
486+
links_definitions
487+
),
478488
"media_auth": True,
479489
"media_check": True,
480490
"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
@@ -1184,124 +1184,70 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
11841184

11851185

11861186
@pytest.mark.parametrize(
1187-
"ancestors_links, select_options",
1187+
"reach, role, select_options",
11881188
[
11891189
# One ancestor
11901190
(
1191-
[{"link_reach": "public", "link_role": "reader"}],
1191+
"public",
1192+
"reader",
11921193
{
11931194
"public": ["reader", "editor"],
11941195
},
11951196
),
1196-
([{"link_reach": "public", "link_role": "editor"}], {"public": ["editor"]}),
1197+
("public", "editor", {"public": ["editor"]}),
11971198
(
1198-
[{"link_reach": "authenticated", "link_role": "reader"}],
1199+
"authenticated",
1200+
"reader",
11991201
{
12001202
"authenticated": ["reader", "editor"],
12011203
"public": ["reader", "editor"],
12021204
},
12031205
),
12041206
(
1205-
[{"link_reach": "authenticated", "link_role": "editor"}],
1207+
"authenticated",
1208+
"editor",
12061209
{"authenticated": ["editor"], "public": ["reader", "editor"]},
12071210
),
12081211
(
1209-
[{"link_reach": "restricted", "link_role": "reader"}],
1212+
"restricted",
1213+
"reader",
12101214
{
12111215
"restricted": None,
12121216
"authenticated": ["reader", "editor"],
12131217
"public": ["reader", "editor"],
12141218
},
12151219
),
12161220
(
1217-
[{"link_reach": "restricted", "link_role": "editor"}],
1221+
"restricted",
1222+
"editor",
12181223
{
12191224
"restricted": None,
12201225
"authenticated": ["reader", "editor"],
12211226
"public": ["reader", "editor"],
12221227
},
12231228
),
1224-
# Multiple ancestors with different roles
1225-
(
1226-
[
1227-
{"link_reach": "public", "link_role": "reader"},
1228-
{"link_reach": "public", "link_role": "editor"},
1229-
],
1230-
{"public": ["editor"]},
1231-
),
1232-
(
1233-
[
1234-
{"link_reach": "authenticated", "link_role": "reader"},
1235-
{"link_reach": "authenticated", "link_role": "editor"},
1236-
],
1237-
{"authenticated": ["editor"], "public": ["reader", "editor"]},
1238-
),
1239-
(
1240-
[
1241-
{"link_reach": "restricted", "link_role": "reader"},
1242-
{"link_reach": "restricted", "link_role": "editor"},
1243-
],
1244-
{
1245-
"restricted": None,
1246-
"authenticated": ["reader", "editor"],
1247-
"public": ["reader", "editor"],
1248-
},
1249-
),
1250-
# Multiple ancestors with different reaches
1229+
# No ancestors (edge case)
12511230
(
1252-
[
1253-
{"link_reach": "authenticated", "link_role": "reader"},
1254-
{"link_reach": "public", "link_role": "reader"},
1255-
],
1231+
"public",
1232+
None,
12561233
{
12571234
"public": ["reader", "editor"],
1235+
"authenticated": ["reader", "editor"],
1236+
"restricted": None,
12581237
},
12591238
),
12601239
(
1261-
[
1262-
{"link_reach": "restricted", "link_role": "reader"},
1263-
{"link_reach": "authenticated", "link_role": "reader"},
1264-
{"link_reach": "public", "link_role": "reader"},
1265-
],
1240+
None,
1241+
"reader",
12661242
{
12671243
"public": ["reader", "editor"],
1268-
},
1269-
),
1270-
# Multiple ancestors with mixed reaches and roles
1271-
(
1272-
[
1273-
{"link_reach": "authenticated", "link_role": "editor"},
1274-
{"link_reach": "public", "link_role": "reader"},
1275-
],
1276-
{"public": ["reader", "editor"]},
1277-
),
1278-
(
1279-
[
1280-
{"link_reach": "authenticated", "link_role": "reader"},
1281-
{"link_reach": "public", "link_role": "editor"},
1282-
],
1283-
{"public": ["editor"]},
1284-
),
1285-
(
1286-
[
1287-
{"link_reach": "restricted", "link_role": "editor"},
1288-
{"link_reach": "authenticated", "link_role": "reader"},
1289-
],
1290-
{
12911244
"authenticated": ["reader", "editor"],
1292-
"public": ["reader", "editor"],
1245+
"restricted": None,
12931246
},
12941247
),
12951248
(
1296-
[
1297-
{"link_reach": "restricted", "link_role": "reader"},
1298-
{"link_reach": "authenticated", "link_role": "editor"},
1299-
],
1300-
{"authenticated": ["editor"], "public": ["reader", "editor"]},
1301-
),
1302-
# No ancestors (edge case)
1303-
(
1304-
[],
1249+
None,
1250+
None,
13051251
{
13061252
"public": ["reader", "editor"],
13071253
"authenticated": ["reader", "editor"],
@@ -1310,9 +1256,9 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
13101256
),
13111257
],
13121258
)
1313-
def test_models_documents_get_select_options(ancestors_links, select_options):
1259+
def test_models_documents_get_select_options(reach, role, select_options):
13141260
"""Validate that the "get_select_options" method operates as expected."""
1315-
assert models.LinkReachChoices.get_select_options(ancestors_links) == select_options
1261+
assert models.LinkReachChoices.get_select_options(reach, role) == select_options
13161262

13171263

13181264
def test_models_documents_compute_ancestors_links_no_highest_readable():

0 commit comments

Comments
 (0)