diff --git a/temba/orgs/models.py b/temba/orgs/models.py index 3b676a2e06..b7a63579bf 100644 --- a/temba/orgs/models.py +++ b/temba/orgs/models.py @@ -458,10 +458,12 @@ class Org(SmartModel): FEATURE_USERS = "users" # can invite users to this org FEATURE_NEW_ORGS = "new_orgs" # can create new workspace with same login FEATURE_CHILD_ORGS = "child_orgs" # can create child workspaces of this org + FEATURE_TEAMS = "teams" # can create teams to organize agent users FEATURES_CHOICES = ( (FEATURE_USERS, _("Users")), (FEATURE_NEW_ORGS, _("New Orgs")), (FEATURE_CHILD_ORGS, _("Child Orgs")), + (FEATURE_TEAMS, _("Teams")), ) LIMIT_CHANNELS = "channels" diff --git a/temba/orgs/tests.py b/temba/orgs/tests.py index ded39eacf1..830edc7ae1 100644 --- a/temba/orgs/tests.py +++ b/temba/orgs/tests.py @@ -1657,8 +1657,8 @@ def test_workspace(self): ], ) - # enable child workspaces and users - self.org.features = [Org.FEATURE_USERS, Org.FEATURE_CHILD_ORGS] + # enable child workspaces, users and teams + self.org.features = [Org.FEATURE_USERS, Org.FEATURE_CHILD_ORGS, Org.FEATURE_TEAMS] self.org.save(update_fields=("features",)) self.child_org = Org.objects.create( @@ -2824,16 +2824,22 @@ def test_list(self): def test_team(self): team_url = reverse("orgs.user_team", args=[self.org.default_ticket_team.id]) - # nobody can access if users feature not enabled + # nobody can access if teams feature not enabled response = self.requestView(team_url, self.admin) self.assertRedirect(response, reverse("orgs.org_workspace")) - self.org.features = [Org.FEATURE_USERS] + self.org.features = [Org.FEATURE_TEAMS] self.org.save(update_fields=("features",)) self.assertRequestDisallowed(team_url, [None, self.user, self.editor, self.agent]) self.assertListFetch(team_url, [self.admin], context_objects=[self.agent]) + self.assertContentMenu(team_url, self.admin, []) # because it's a system team + + team = Team.create(self.org, self.admin, "My Team") + team_url = reverse("orgs.user_team", args=[team.id]) + + self.assertContentMenu(team_url, self.admin, ["Edit", "Delete"]) def test_update(self): update_url = reverse("orgs.user_update", args=[self.agent.id]) diff --git a/temba/orgs/views/views.py b/temba/orgs/views/views.py index 3cc0d1e947..96cc76f91f 100644 --- a/temba/orgs/views/views.py +++ b/temba/orgs/views/views.py @@ -380,7 +380,7 @@ def get_context_data(self, **kwargs): class Team(RequireFeatureMixin, ContextMenuMixin, BaseListView): permission = "orgs.user_list" - require_feature = Org.FEATURE_USERS + require_feature = Org.FEATURE_TEAMS menu_path = "/settings/teams" search_fields = ("email__icontains", "first_name__icontains", "last_name__icontains") @@ -1064,14 +1064,15 @@ def derive_menu(self): count=org.invitations.filter(is_active=True).count(), ) ) - menu.append( - self.create_menu_item( - name=_("Teams"), - icon="agent", - href="tickets.team_list", - count=org.teams.filter(is_active=True).count(), + if Org.FEATURE_TEAMS in org.features: + menu.append( + self.create_menu_item( + name=_("Teams"), + icon="agent", + href="tickets.team_list", + count=org.teams.filter(is_active=True).count(), + ) ) - ) menu.append(self.create_divider()) if self.has_org_perm("orgs.org_export"): diff --git a/temba/tickets/tests.py b/temba/tickets/tests.py index f75e7e2b40..5dbc393a32 100644 --- a/temba/tickets/tests.py +++ b/temba/tickets/tests.py @@ -9,7 +9,7 @@ from django.utils import timezone from temba.contacts.models import Contact, ContactField, ContactURN -from temba.orgs.models import Export, OrgMembership, OrgRole +from temba.orgs.models import Export, Org, OrgMembership, OrgRole from temba.tests import CRUDLTestMixin, MigrationTest, TembaTest, matchers, mock_mailroom from temba.utils.dates import datetime_to_timestamp from temba.utils.uuid import uuid4 @@ -436,6 +436,13 @@ class TeamCRUDLTest(TembaTest, CRUDLTestMixin): def test_create(self): create_url = reverse("tickets.team_create") + # nobody can access if new orgs feature not enabled + response = self.requestView(create_url, self.admin) + self.assertRedirect(response, reverse("orgs.org_workspace")) + + self.org.features = [Org.FEATURE_TEAMS] + self.org.save(update_fields=("features",)) + self.assertRequestDisallowed(create_url, [None, self.agent, self.user, self.editor]) self.assertCreateFetch(create_url, [self.admin], form_fields=("name", "topics")) @@ -557,6 +564,13 @@ def test_list(self): list_url = reverse("tickets.team_list") + # nobody can access if new orgs feature not enabled + response = self.requestView(list_url, self.admin) + self.assertRedirect(response, reverse("orgs.org_workspace")) + + self.org.features = [Org.FEATURE_TEAMS] + self.org.save(update_fields=("features",)) + self.assertRequestDisallowed(list_url, [None, self.agent, self.editor]) self.assertListFetch(list_url, [self.admin], context_objects=[self.org.default_ticket_team, team2, team1]) diff --git a/temba/tickets/views.py b/temba/tickets/views.py index 3e9c92f71a..1be7c1f21e 100644 --- a/temba/tickets/views.py +++ b/temba/tickets/views.py @@ -13,6 +13,7 @@ from temba.msgs.models import Msg from temba.notifications.views import NotificationTargetMixin +from temba.orgs.models import Org from temba.orgs.views.base import ( BaseCreateModal, BaseDeleteModal, @@ -21,7 +22,7 @@ BaseMenuView, BaseUpdateModal, ) -from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin +from temba.orgs.views.mixins import OrgObjPermsMixin, OrgPermsMixin, RequireFeatureMixin from temba.utils.dates import datetime_to_timestamp, timestamp_to_datetime from temba.utils.export import response_from_workbook from temba.utils.fields import InputWidget @@ -112,7 +113,8 @@ class TeamCRUDL(SmartCRUDL): model = Team actions = ("create", "update", "delete", "list") - class Create(BaseCreateModal): + class Create(RequireFeatureMixin, BaseCreateModal): + require_feature = Org.FEATURE_TEAMS form_class = TeamForm success_url = "@tickets.team_list" @@ -127,7 +129,8 @@ class Delete(BaseDeleteModal): cancel_url = "id@orgs.user_team" redirect_url = "@tickets.team_list" - class List(ContextMenuMixin, BaseListView): + class List(RequireFeatureMixin, ContextMenuMixin, BaseListView): + require_feature = Org.FEATURE_TEAMS menu_path = "/settings/teams" def derive_queryset(self, **kwargs):