Skip to content

Commit 21a8782

Browse files
Merge pull request #111 from unicef/bugfix/248879-user_role-permission-second-fix
AB#248879: UserRole permission
2 parents d4218b4 + 4dbe4bb commit 21a8782

File tree

2 files changed

+96
-9
lines changed

2 files changed

+96
-9
lines changed

src/country_workspace/workspaces/forms.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from operator import attrgetter
12
from typing import TYPE_CHECKING, Any
23

34
from dateutil.utils import today
@@ -9,11 +10,12 @@
910
from django.utils.translation import gettext_lazy as _
1011
from django_select2 import forms as s2forms
1112

12-
from ..models import Program
13+
from ..models import Program, Office, User
1314
from ..state import state
1415
from .config import conf
1516

1617
if TYPE_CHECKING:
18+
from collections.abc import Iterable
1719
from django.contrib.auth.base_user import AbstractBaseUser
1820

1921

@@ -65,6 +67,23 @@ def media(self) -> forms.Media:
6567
)
6668

6769

70+
def get_available_programs(office: Office, user: User) -> QuerySet[Program]:
71+
program_qs = office.programs.filter(enabled=True)
72+
73+
if not user.is_superuser:
74+
roles = tuple(user.roles.filter(Q(expires=None) | Q(expires__gt=today()), country_office=office))
75+
if (roles_count := len(roles)) > 0:
76+
# if there is only one role with program_id == None, user can access all programs,
77+
# otherwise we allow access to programs assigned to roles
78+
if roles_count > 1 or roles[0].program_id:
79+
program_ids: "Iterable[int]" = filter(None, map(attrgetter("program_id"), roles))
80+
program_qs = program_qs.filter(id__in=program_ids)
81+
else:
82+
program_qs = Program.objects.none()
83+
84+
return program_qs.order_by("name").all()
85+
86+
6887
class SelectProgramForm(forms.Form):
6988
program = forms.ModelChoiceField(
7089
label=_("Program"),
@@ -86,11 +105,4 @@ def __init__(self, *args: "Any", **kwargs: "Any") -> None:
86105
self.request = kwargs.pop("request")
87106
super().__init__(*args, **kwargs)
88107
if state.tenant:
89-
program_qs = state.tenant.programs.filter(enabled=True)
90-
if not state.request.user.is_superuser:
91-
roles = state.request.user.roles.filter(Q(expires=None) | Q(expires__gt=today()))
92-
has_all_programs_access = roles.filter(program=None).exists()
93-
if not has_all_programs_access:
94-
program_qs = program_qs.filter(id__in=roles.values("program_id"))
95-
96-
self.fields["program"].queryset = program_qs.order_by("name").all()
108+
self.fields["program"].queryset = get_available_programs(state.tenant, state.request.user)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import pytest
2+
3+
from country_workspace.models import Office, User, Program, UserRole
4+
from country_workspace.workspaces.forms import get_available_programs
5+
from testutils.factories import OfficeFactory, UserFactory, ProgramFactory, UserRoleFactory, SuperUserFactory
6+
7+
8+
@pytest.fixture
9+
def office() -> Office:
10+
return OfficeFactory()
11+
12+
13+
@pytest.fixture
14+
def program0(office: Office) -> Program:
15+
return ProgramFactory(country_office=office)
16+
17+
18+
@pytest.fixture
19+
def program1(office: Office) -> Program:
20+
return ProgramFactory(country_office=office)
21+
22+
23+
@pytest.fixture
24+
def all_programs(program0: Program, program1: Program) -> tuple[Program, ...]:
25+
return program0, program1
26+
27+
28+
@pytest.fixture
29+
def user() -> User:
30+
return UserFactory()
31+
32+
33+
@pytest.fixture
34+
def single_program_role(office: Office, user: User, program0: Program) -> UserRole:
35+
return UserRoleFactory(user=user, country_office=office, program=program0)
36+
37+
38+
@pytest.fixture
39+
def none_program_role(office: Office, user: User) -> Program:
40+
return UserRoleFactory(country_office=office, user=user)
41+
42+
43+
@pytest.fixture
44+
def super_user() -> User:
45+
return SuperUserFactory()
46+
47+
48+
def test_no_roles(office: Office, all_programs: tuple[Program, ...], user: User) -> None:
49+
assert get_available_programs(office, user).count() == 0
50+
51+
52+
def test_single_program_role(
53+
office: Office, all_programs: tuple[Program, ...], single_program_role: UserRole, user: User
54+
) -> None:
55+
assert get_available_programs(office, user).count() == 1
56+
57+
58+
def test_none_program_role(
59+
office: Office, all_programs: tuple[Program, ...], none_program_role: Program, user: User
60+
) -> None:
61+
assert get_available_programs(office, user).count() == len(all_programs)
62+
63+
64+
def test_single_program_role_and_none_program_role(
65+
office: Office,
66+
all_programs: tuple[Program, ...],
67+
single_program_role: UserRole,
68+
none_program_role: UserRole,
69+
user: User,
70+
) -> None:
71+
assert get_available_programs(office, user).count() == 1
72+
73+
74+
def test_super_user(office: Office, all_programs: tuple[Program, ...], program1: Program, super_user: User) -> None:
75+
assert get_available_programs(office, super_user).count() == len(all_programs)

0 commit comments

Comments
 (0)