Skip to content

Commit 1ba470a

Browse files
authored
Merge pull request #1139 from thunderstore-io/refactor-team-form-views
Refactor team disband and create views(TS-2426)
2 parents 2e7df1c + 988b7e8 commit 1ba470a

File tree

9 files changed

+182
-78
lines changed

9 files changed

+182
-78
lines changed

django/thunderstore/api/cyberstorm/services/team.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
1-
from django.core.exceptions import ValidationError
21
from django.db import transaction
3-
from django.shortcuts import get_object_or_404
42

53
from thunderstore.account.models import ServiceAccount
64
from thunderstore.core.exceptions import PermissionValidationError
75
from thunderstore.core.types import UserType
8-
from thunderstore.repository.models import Namespace, Team, TeamMember
6+
from thunderstore.repository.models import Team, TeamMember
97
from thunderstore.repository.models.team import TeamMemberRole
108

119

1210
@transaction.atomic
13-
def disband_team(user: UserType, team_name: str) -> None:
14-
teams = Team.objects.exclude(is_active=False)
15-
team = get_object_or_404(teams, name=team_name)
16-
team.ensure_user_can_access(user)
17-
team.ensure_user_can_disband(user)
11+
def disband_team(agent: UserType, team: Team) -> None:
12+
team.ensure_user_can_access(agent)
13+
team.ensure_user_can_disband(agent)
1814
team.delete()
1915

2016

2117
@transaction.atomic
22-
def create_team(user: UserType, team_name: str) -> Team:
23-
if not user or not user.is_authenticated or not user.is_active:
18+
def create_team(agent: UserType, team_name: str) -> Team:
19+
if not agent or not agent.is_authenticated or not agent.is_active:
2420
raise PermissionValidationError("Must be authenticated to create teams")
25-
if getattr(user, "service_account", None) is not None:
21+
if getattr(agent, "service_account", None) is not None:
2622
raise PermissionValidationError("Service accounts cannot create teams")
27-
if Team.objects.filter(name=team_name).exists():
28-
raise ValidationError("A team with the provided name already exists")
29-
if Namespace.objects.filter(name=team_name).exists():
30-
raise ValidationError("A namespace with the provided name already exists")
3123

32-
team = Team.objects.create(name=team_name)
33-
team.add_member(user=user, role=TeamMemberRole.owner)
24+
team = Team.create(name=team_name)
25+
team.add_member(user=agent, role=TeamMemberRole.owner)
3426
return team
3527

3628

django/thunderstore/api/cyberstorm/tests/services/test_team_services.py

Lines changed: 86 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import pytest
22
from django.core.exceptions import ValidationError
3-
from django.http import Http404
43

54
from conftest import TestUserTypes
65
from thunderstore.api.cyberstorm.services import team as team_services
@@ -13,60 +12,91 @@
1312
@pytest.mark.django_db
1413
def test_disband_team_success(team_owner):
1514
team_pk = team_owner.team.pk
16-
team_services.disband_team(team_owner.user, team_owner.team.name)
15+
team_services.disband_team(agent=team_owner.user, team=team_owner.team)
1716
assert not Team.objects.filter(pk=team_pk).exists()
1817

1918

20-
@pytest.mark.django_db
21-
def test_disband_team_team_not_found(user):
22-
with pytest.raises(Http404):
23-
team_services.disband_team(user, "NonExistentTeam")
24-
25-
2619
@pytest.mark.django_db
2720
def test_disband_team_user_cannot_access_team(user):
2821
team = Team.objects.create(name="TestTeam")
29-
with pytest.raises(ValidationError, match="Must be a member to access team"):
30-
team_services.disband_team(user, team.name)
22+
error_msg = "Must be a member to access team"
23+
with pytest.raises(PermissionValidationError, match=error_msg):
24+
team_services.disband_team(agent=user, team=team)
3125

3226

3327
@pytest.mark.django_db
3428
def test_disband_team_user_cannot_disband(team_member):
35-
with pytest.raises(ValidationError, match="Must be an owner to disband team"):
36-
team_services.disband_team(team_member.user, team_member.team.name)
29+
error_msg = "Must be an owner to disband team"
30+
with pytest.raises(PermissionValidationError, match=error_msg):
31+
team_services.disband_team(agent=team_member.user, team=team_member.team)
32+
33+
34+
@pytest.mark.django_db
35+
def test_disband_team_with_packages(package, team_owner):
36+
team = team_owner.team
37+
package.owner = team
38+
package.save()
39+
40+
with pytest.raises(ValidationError, match="Unable to disband teams with packages"):
41+
team_services.disband_team(agent=team_owner.user, team=team)
42+
43+
44+
@pytest.mark.django_db
45+
def test_disband_team_user_is_service_account(service_account, team):
46+
service_account_user = service_account.user
47+
error_msg = "Service accounts are unable to perform this action"
48+
with pytest.raises(PermissionValidationError, match=error_msg):
49+
team_services.disband_team(agent=service_account_user, team=team)
50+
51+
52+
@pytest.mark.django_db
53+
def test_disband_team_user_not_authenticated(team):
54+
error_msg = "Must be authenticated"
55+
with pytest.raises(PermissionValidationError, match=error_msg):
56+
team_services.disband_team(agent=None, team=team)
57+
58+
59+
@pytest.mark.django_db
60+
def test_disband_team_user_not_active(user, team):
61+
user.is_active = False
62+
user.save()
63+
64+
error_msg = "User has been deactivated"
65+
with pytest.raises(PermissionValidationError, match=error_msg):
66+
team_services.disband_team(agent=user, team=team)
3767

3868

3969
@pytest.mark.django_db
4070
def test_create_team_name_exists_in_team(user):
4171
Team.objects.create(name="existing_team")
4272

43-
error_msg = "A team with the provided name already exists"
73+
error_msg = "Team with this name already exists"
4474
with pytest.raises(ValidationError, match=error_msg):
45-
team_services.create_team(user, "existing_team")
75+
team_services.create_team(agent=user, team_name="existing_team")
4676

4777

4878
@pytest.mark.django_db
4979
def test_create_team_name_exists_in_namespace(user):
5080
Namespace.objects.create(name="existing_namespace")
5181

52-
error_msg = "A namespace with the provided name already exists"
82+
error_msg = "Namespace with this name already exists"
5383
with pytest.raises(ValidationError, match=error_msg):
54-
team_services.create_team(user, "existing_namespace")
84+
team_services.create_team(agent=user, team_name="existing_namespace")
5585

5686

5787
@pytest.mark.django_db
5888
def test_create_team_user_is_service_account(service_account):
5989
service_account_user = service_account.user
6090

6191
error_msg = "Service accounts cannot create teams"
62-
with pytest.raises(ValidationError, match=error_msg):
63-
team_services.create_team(service_account_user, "new_team")
92+
with pytest.raises(PermissionValidationError, match=error_msg):
93+
team_services.create_team(agent=service_account_user, team_name="new_team")
6494

6595

6696
@pytest.mark.django_db
6797
def test_create_team_success(user):
6898
team_name = "new_team"
69-
team = team_services.create_team(user, team_name)
99+
team = team_services.create_team(agent=user, team_name=team_name)
70100

71101
assert Team.objects.filter(name=team_name).exists()
72102
assert team.name == team_name
@@ -330,3 +360,40 @@ def test_update_team_member_cannot_remove_last_owner(team_owner):
330360
team_services.update_team_member(
331361
team_owner.user, team_owner, TeamMemberRole.member
332362
)
363+
364+
365+
@pytest.mark.django_db
366+
def test_create_team_user_not_authenticated():
367+
error_msg = "Must be authenticated to create teams"
368+
with pytest.raises(PermissionValidationError, match=error_msg):
369+
team_services.create_team(agent=None, team_name="new_team")
370+
371+
372+
@pytest.mark.django_db
373+
def test_create_team_user_not_active(user):
374+
user.is_active = False
375+
user.save()
376+
377+
error_msg = "Must be authenticated to create teams"
378+
with pytest.raises(PermissionValidationError, match=error_msg):
379+
team_services.create_team(agent=user, team_name="new_team")
380+
381+
382+
@pytest.mark.django_db
383+
@pytest.mark.parametrize(
384+
("name1", "name2", "should_fail"),
385+
(
386+
("Team", "team", True),
387+
("Team", "t_eam", False),
388+
("team", "teaM", True),
389+
("team", "team", True),
390+
),
391+
)
392+
def test_create_team_name_conflict(user, name1: str, name2: str, should_fail: bool):
393+
Team.create(name=name1)
394+
if should_fail:
395+
with pytest.raises(ValidationError):
396+
team_services.create_team(agent=user, team_name=name2)
397+
else:
398+
team = team_services.create_team(agent=user, team_name=name2)
399+
assert team.name == name2

django/thunderstore/api/cyberstorm/tests/test_create_team.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ def test_create_team__fail_because_team_with_provided_name_exists(
5353
):
5454
api_client.force_authenticate(user)
5555
response = make_request(api_client, team.name)
56-
expected_response = {
57-
"non_field_errors": ["A team with the provided name already exists"]
58-
}
56+
expected_response = {"non_field_errors": ["Team with this name already exists"]}
5957

6058
assert response.status_code == 400
6159
assert response.json() == expected_response
@@ -70,7 +68,7 @@ def test_create_team__fail_because_team_with_provided_namespace_exists(
7068
NamespaceFactory(name="CoolestTeamNameEver")
7169
response = make_request(api_client, "CoolestTeamNameEver")
7270
expected_response = {
73-
"non_field_errors": ["A namespace with the provided name already exists"]
71+
"non_field_errors": ["Namespace with this name already exists"]
7472
}
7573

7674
assert response.status_code == 400

django/thunderstore/api/cyberstorm/views/team.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ class TeamCreateAPIView(APIView):
7575
def post(self, request, *args, **kwargs):
7676
serializer = CyberstormCreateTeamSerializer(data=request.data)
7777
serializer.is_valid(raise_exception=True)
78+
7879
team_name = serializer.validated_data["name"]
79-
team = create_team(user=request.user, team_name=team_name)
80+
team = create_team(agent=request.user, team_name=team_name)
81+
8082
return_data = CyberstormTeamSerializer(team).data
8183
return Response(return_data, status=status.HTTP_201_CREATED)
8284

@@ -166,8 +168,8 @@ class DisbandTeamAPIView(APIView):
166168
responses={status.HTTP_204_NO_CONTENT: ""},
167169
)
168170
def delete(self, request, *args, **kwargs):
169-
team_name = kwargs["team_name"]
170-
disband_team(user=request.user, team_name=team_name)
171+
team = get_object_or_404(Team, name=kwargs["team_name"])
172+
disband_team(agent=request.user, team=team)
171173
return Response(status=status.HTTP_204_NO_CONTENT)
172174

173175

django/thunderstore/repository/forms/team.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,17 @@
33
from django import forms
44
from django.contrib.auth import get_user_model
55
from django.core.exceptions import ObjectDoesNotExist, ValidationError
6+
from django.db import transaction
67

78
from thunderstore.api.cyberstorm.services.team import (
9+
create_team,
10+
disband_team,
811
remove_team_member,
912
update_team,
1013
update_team_member,
1114
)
12-
from thunderstore.core.exceptions import PermissionValidationError
1315
from thunderstore.core.types import UserType
14-
from thunderstore.repository.models import (
15-
Namespace,
16-
Team,
17-
TeamMember,
18-
TeamMemberRole,
19-
transaction,
20-
)
16+
from thunderstore.repository.models import Team, TeamMember, TeamMemberRole
2117
from thunderstore.repository.validators import PackageReferenceComponentValidator
2218

2319
User = get_user_model()
@@ -38,23 +34,22 @@ def __init__(self, user: UserType, *args, **kwargs):
3834

3935
def clean_name(self):
4036
name = self.cleaned_data["name"]
41-
if Team.objects.filter(name__iexact=name.lower()).exists():
42-
raise ValidationError(f"A team with the provided name already exists")
43-
if Namespace.objects.filter(name__iexact=name.lower()).exists():
44-
raise ValidationError("A namespace with the provided name already exists")
37+
if Team.objects.filter(name__iexact=name).exists():
38+
raise ValidationError("Team with this name already exists")
4539
return name
4640

47-
def clean(self):
48-
if not self.user or not self.user.is_authenticated or not self.user.is_active:
49-
raise PermissionValidationError("Must be authenticated to create teams")
50-
if getattr(self.user, "service_account", None) is not None:
51-
raise PermissionValidationError("Service accounts cannot create teams")
52-
return super().clean()
53-
5441
@transaction.atomic
5542
def save(self, *args, **kwargs) -> Team:
56-
instance = super().save()
57-
instance.add_member(user=self.user, role=TeamMemberRole.owner)
43+
if self.errors:
44+
raise ValidationError(self.errors)
45+
46+
try:
47+
team_name = self.cleaned_data["name"]
48+
instance = create_team(agent=self.user, team_name=team_name)
49+
except ValidationError as e:
50+
self.add_error(None, e)
51+
raise ValidationError(self.errors)
52+
5853
return instance
5954

6055

@@ -169,13 +164,17 @@ def clean_verification(self):
169164
def clean(self):
170165
if not self.instance.pk:
171166
raise ValidationError("Missing team instance")
172-
self.instance.ensure_user_can_disband(self.user)
173167
return super().clean()
174168

175169
@transaction.atomic
176170
def save(self, **kwargs):
177-
self.instance.ensure_user_can_disband(self.user)
178-
self.instance.delete()
171+
if self.errors:
172+
raise ValidationError(self.errors)
173+
try:
174+
disband_team(agent=self.user, team=self.instance)
175+
except ValidationError as e:
176+
self.add_error(None, e)
177+
raise ValidationError(self.errors)
179178

180179

181180
class DonationLinkTeamForm(forms.ModelForm):

django/thunderstore/repository/models/team.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,9 @@ def add_member(self, user: UserType, role: str) -> TeamMember:
187187
@classmethod
188188
@transaction.atomic
189189
def create(cls, name, **kwargs):
190-
existing_ns = Namespace.objects.filter(name__iexact=name).first()
191-
if existing_ns:
192-
raise ValidationError("Namespace with the Teams name exists")
193-
else:
194-
team = cls.objects.create(name=name, **kwargs)
195-
Namespace.objects.create(name=name, team=team)
196-
return team
190+
team = cls.objects.create(name=name, **kwargs)
191+
Namespace.objects.create(name=name, team=team)
192+
return team
197193

198194
@classmethod
199195
@transaction.atomic

django/thunderstore/repository/tests/test_team.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def test_team_create_namespace_creation() -> None:
7575
ns.save()
7676
with pytest.raises(ValidationError) as e:
7777
Team.create(name="taken_namespace")
78-
assert "Namespace with the Teams name exists" in str(e.value)
78+
assert "Namespace with this name already exists" in str(e.value)
7979

8080

8181
@pytest.mark.django_db
@@ -769,3 +769,39 @@ def test_team_ensure_user_can_manage_packages(
769769
else:
770770
assert team.can_user_manage_packages(user) is True
771771
assert team.ensure_user_can_manage_packages(user) is None
772+
773+
774+
@pytest.mark.django_db
775+
def test_team_create__success():
776+
Team.create(name="TestTeam")
777+
assert Team.objects.filter(name="TestTeam").count() == 1
778+
assert Namespace.objects.filter(name="TestTeam").count() == 1
779+
780+
781+
@pytest.mark.django_db
782+
def test_team_create__team_exists_fail(team):
783+
with pytest.raises(ValidationError) as e:
784+
Team.create(name=team.name)
785+
assert "Team with this name already exists" in str(e.value)
786+
assert Team.objects.filter(name=team.name).count() == 1
787+
assert Namespace.objects.filter(name=team.name).count() == 1
788+
789+
790+
@pytest.mark.django_db
791+
def test_team_create__namespace_exists_fail():
792+
NamespaceFactory.create(name="TestTeam")
793+
with pytest.raises(ValidationError) as e:
794+
Team.create(name="TestTeam")
795+
assert "Namespace with this name already exists" in str(e.value)
796+
assert Team.objects.filter(name="TestTeam").count() == 0
797+
assert Namespace.objects.filter(name="TestTeam").count() == 1
798+
799+
800+
@pytest.mark.django_db
801+
def test_team_create__team_name_read_only_fail(team):
802+
with pytest.raises(ValidationError) as e:
803+
team.name = "NewName"
804+
team.save()
805+
assert "Team name is read only" in str(e.value)
806+
team.refresh_from_db()
807+
assert team.name != "NewName"

0 commit comments

Comments
 (0)