diff --git a/apps/hosts/utils.py b/apps/hosts/utils.py index 596c48fcae..980afbe7a1 100644 --- a/apps/hosts/utils.py +++ b/apps/hosts/utils.py @@ -3,6 +3,8 @@ from .models import ChallengeHost, ChallengeHostTeam +get_challenge_host_team_model = get_model_object(ChallengeHostTeam) + def get_challenge_host_teams_for_user(user): """Returns challenge host team ids for a particular user""" @@ -26,6 +28,3 @@ def is_user_part_of_host_team(user, host_team): return ChallengeHost.objects.filter( user=user, team_name=host_team ).exists() - - -get_challenge_host_team_model = get_model_object(ChallengeHostTeam) diff --git a/apps/hosts/views.py b/apps/hosts/views.py index 4b04126650..939170d2d9 100644 --- a/apps/hosts/views.py +++ b/apps/hosts/views.py @@ -15,6 +15,7 @@ from accounts.permissions import HasVerifiedEmail from base.utils import get_model_object, team_paginated_queryset +from hosts.utils import get_challenge_host_team_model from .filters import HostTeamsFilter from .models import ChallengeHost, ChallengeHostTeam from .serializers import ( @@ -161,14 +162,7 @@ def challenge_host_list(request, challenge_host_team_pk): @permission_classes((permissions.IsAuthenticated, HasVerifiedEmail)) @authentication_classes((ExpiringTokenAuthentication,)) def challenge_host_detail(request, challenge_host_team_pk, pk): - try: - challenge_host_team = ChallengeHostTeam.objects.get( - pk=challenge_host_team_pk - ) - except ChallengeHostTeam.DoesNotExist: - response_data = {"error": "ChallengeHostTeam does not exist"} - return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE) - + challenge_host_team = get_challenge_host_team_model(challenge_host_team_pk) challenge_host = get_challenge_host_model(pk) if request.method == "GET": @@ -201,13 +195,24 @@ def challenge_host_detail(request, challenge_host_team_pk, pk): response_data = serializer.data return Response(response_data, status=status.HTTP_200_OK) else: - return Response( - serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == "DELETE": - challenge_host.delete() - return Response(status=status.HTTP_204_NO_CONTENT) + if challenge_host_team.created_by == request.user: + + if (challenge_host.user == request.user): # when the user tries to remove himself + response_data = { + "error": "You are not allowed to remove yourself since you are the team admin. Please delete the team if you want to do so!" + } + return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE) + else: + challenge_host.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + else: + response_data = { + "error": "Sorry, you do not have permissions to remove this challenge host" + } + return Response(response_data, status=status.HTTP_401_UNAUTHORIZED) @api_view(["POST"]) diff --git a/apps/participants/serializers.py b/apps/participants/serializers.py index 2e255857bd..90ddb3af9e 100644 --- a/apps/participants/serializers.py +++ b/apps/participants/serializers.py @@ -69,7 +69,7 @@ class ParticipantSerializer(serializers.ModelSerializer): class Meta: model = Participant - fields = ("member_name", "status", "member_id") + fields = ("member_name", "status", "member_id", "id") def get_member_name(self, obj): return obj.user.username diff --git a/apps/participants/views.py b/apps/participants/views.py index 1a790a90b2..b2e2f8ecc0 100644 --- a/apps/participants/views.py +++ b/apps/participants/views.py @@ -304,7 +304,7 @@ def delete_participant_from_team(request, participant_team_pk, participant_pk): ): # when the user tries to remove himself response_data = { "error": "You are not allowed to remove yourself since you are admin. Please delete the team if you want to do so!" - } # noqa: ignore=E501 + } return Response( response_data, status=status.HTTP_406_NOT_ACCEPTABLE ) diff --git a/frontend/src/js/controllers/challengeHostTeamsCtrl.js b/frontend/src/js/controllers/challengeHostTeamsCtrl.js index 64057ec892..560db7ba9c 100644 --- a/frontend/src/js/controllers/challengeHostTeamsCtrl.js +++ b/frontend/src/js/controllers/challengeHostTeamsCtrl.js @@ -369,6 +369,82 @@ }, function() {}); }; + // Delete a particular host + vm.confirmMemberDelete = function (ev, hostTeamId, hostId) { + ev.stopPropagation(); + // Appending dialog to document.body to cover sidenav in docs app + var confirm = $mdDialog.confirm() + .title('Would you like to remove this member?') + .textContent('Note: This action will remove the member from team.') + .ariaLabel('Lucky day') + .targetEvent(ev) + .ok('Yes') + .cancel("No"); + + $mdDialog.show(confirm).then(function () { + vm.startLoader(); + var parameters = {}; + parameters.url = 'hosts/challenge_host_team/' + hostTeamId + '/challenge_host/' + hostId; + parameters.method = 'DELETE'; + parameters.data = {}; + parameters.token = userKey; + parameters.callback = { + onSuccess: function () { + vm.team.error = false; + $rootScope.notify("info", "Member removed successfully"); + var parameters = {}; + parameters.url = 'hosts/challenge_host_team'; + parameters.method = 'GET'; + parameters.token = userKey; + parameters.callback = { + onSuccess: function (response) { + var status = response.status; + var details = response.data; + if (status == 200) { + vm.existTeam = details; + + // condition for pagination + if (vm.existTeam.next === null) { + vm.isNext = 'disabled'; + vm.currentPage = vm.existTeam.count / 10; + } else { + vm.isNext = ''; + vm.currentPage = parseInt(vm.existTeam.next.split('page=')[1] - 1); + } + + if (vm.existTeam.previous === null) { + vm.isPrev = 'disabled'; + } else { + vm.isPrev = ''; + } + + if (vm.existTeam.count === 0) { + vm.showPagination = false; + vm.paginationMsg = "No team exists for now. Start by creating a new team!"; + } else { + vm.showPagination = true; + vm.paginationMsg = ""; + } + } + + vm.stopLoader(); + } + }; + utilities.sendRequest(parameters); + }, + onError: function (response) { + var error = response.data['error']; + vm.stopLoader(); + $rootScope.notify("error", error); + } + }; + + utilities.sendRequest(parameters); + + }, function () { + }); + }; + vm.inviteOthers = function(ev, hostTeamId) { ev.stopPropagation(); // Appending dialog to document.body diff --git a/frontend/src/js/controllers/teamsCtrl.js b/frontend/src/js/controllers/teamsCtrl.js index 82d37e6f0b..1880009aec 100644 --- a/frontend/src/js/controllers/teamsCtrl.js +++ b/frontend/src/js/controllers/teamsCtrl.js @@ -244,6 +244,7 @@ }; + // Delete participant along with members vm.confirmDelete = function(ev, participantTeamId) { ev.stopPropagation(); // Appending dialog to document.body to cover sidenav in docs app @@ -324,6 +325,84 @@ }); }; + // Delete only a particular member + vm.confirmMemberDelete = function (ev, participantTeamId, participantId) { + ev.stopPropagation(); + // Appending dialog to document.body to cover sidenav in docs app + var confirm = $mdDialog.confirm() + .title('Would you like to remove this member?') + .textContent('Note: This action will remove the member from team.') + .ariaLabel('Lucky day') + .targetEvent(ev) + .ok('Yes') + .cancel("No"); + + $mdDialog.show(confirm).then(function () { + vm.startLoader(); + var parameters = {}; + parameters.url = 'participants/participant_team/' + participantTeamId + '/participant/' + participantId; + parameters.method = 'DELETE'; + parameters.data = {}; + parameters.token = userKey; + parameters.callback = { + onSuccess: function () { + + vm.team.error = false; + $rootScope.notify("info", "Member removed successfully"); + + var parameters = {}; + parameters.url = 'participants/participant_team'; + parameters.method = 'GET'; + parameters.token = userKey; + parameters.callback = { + onSuccess: function (response) { + var status = response.status; + var details = response.data; + if (status == 200) { + vm.existTeam = details; + + // condition for pagination + if (vm.existTeam.next === null) { + vm.isNext = 'disabled'; + vm.currentPage = vm.existTeam.count / 10; + } else { + vm.isNext = ''; + vm.currentPage = parseInt(vm.existTeam.next.split('page=')[1] - 1); + } + + if (vm.existTeam.previous === null) { + vm.isPrev = 'disabled'; + } else { + vm.isPrev = ''; + } + + if (vm.existTeam.count === 0) { + vm.showPagination = false; + vm.paginationMsg = "No team exists for now. Start by creating a new team!"; + } else { + vm.showPagination = true; + vm.paginationMsg = ""; + } + } + + vm.stopLoader(); + } + }; + utilities.sendRequest(parameters); + }, + onError: function (response) { + var error = response.data['error']; + vm.stopLoader(); + $rootScope.notify("error", error); + } + }; + + utilities.sendRequest(parameters); + + }, function () { + }); + }; + // End of delete member vm.inviteOthers = function(ev, participantTeamId) { ev.stopPropagation(); diff --git a/frontend/src/views/web/challenge-host-teams.html b/frontend/src/views/web/challenge-host-teams.html index d9ff46b4c3..b35223fbfd 100644 --- a/frontend/src/views/web/challenge-host-teams.html +++ b/frontend/src/views/web/challenge-host-teams.html @@ -33,7 +33,9 @@
diff --git a/frontend/src/views/web/teams.html b/frontend/src/views/web/teams.html index 38825f5f2d..f0f79140f2 100644 --- a/frontend/src/views/web/teams.html +++ b/frontend/src/views/web/teams.html @@ -31,7 +31,9 @@ diff --git a/frontend/tests/controllers-test/challengeHostTeamsCtrl.test.js b/frontend/tests/controllers-test/challengeHostTeamsCtrl.test.js index 5b2de035d0..d00cf65e6c 100644 --- a/frontend/tests/controllers-test/challengeHostTeamsCtrl.test.js +++ b/frontend/tests/controllers-test/challengeHostTeamsCtrl.test.js @@ -429,6 +429,31 @@ describe('Unit tests for challenge host team controller', function () { }); }); + describe('Unit tests for confirmMemberDelete function', function () { + beforeEach(function () { + spyOn($mdDialog, 'show').and.callFake(function () { + var deferred = $injector.get('$q').defer(); + return deferred.promise; + }); + }); + + it('open dialog to confirm delete', function () { + var hostTeamId = 1; + var hostId = 1; + var ev = new Event('$click'); + var confirm = $mdDialog.confirm() + .title('Would you like to remove this member?') + .textContent('Note: This action will remove the member from team.') + .ariaLabel('Lucky day') + .targetEvent(ev) + .ok('Yes') + .cancel("No"); + vm.confirmMemberDelete(ev, hostTeamId, hostId); + expect($mdDialog.show).toHaveBeenCalledWith(confirm); + }); + }); + + describe('Unit tests for inviteOthers function', function () { beforeEach(function () { spyOn($mdDialog, 'show').and.callFake(function () { diff --git a/frontend/tests/controllers-test/teamsCtrl.test.js b/frontend/tests/controllers-test/teamsCtrl.test.js index 23ecc21d5f..5244542282 100644 --- a/frontend/tests/controllers-test/teamsCtrl.test.js +++ b/frontend/tests/controllers-test/teamsCtrl.test.js @@ -327,6 +327,31 @@ describe('Unit tests for teams controller', function () { }); }); + describe('Unit tests for confirmMemberDelete function', function () { + beforeEach(function () { + spyOn($mdDialog, 'show').and.callFake(function () { + var deferred = $injector.get('$q').defer(); + return deferred.promise; + }); + }); + + it('open dialog to confirm delete', function () { + var participantTeamId = 1; + var participantId = 1; + var ev = new Event('$click'); + var confirm = $mdDialog.confirm() + .title('Would you like to remove this member?') + .textContent('Note: This action will remove the member from team.') + .ariaLabel('Lucky day') + .targetEvent(ev) + .ok('Yes') + .cancel("No"); + vm.confirmMemberDelete(ev, participantTeamId, participantId); + expect($mdDialog.show).toHaveBeenCalledWith(confirm); + }); + }); + + describe('Unit tests for inviteOthers function', function () { beforeEach(function () { spyOn($mdDialog, 'show').and.callFake(function () { diff --git a/tests/unit/hosts/test_views.py b/tests/unit/hosts/test_views.py index 6f99ddf376..27de55a939 100644 --- a/tests/unit/hosts/test_views.py +++ b/tests/unit/hosts/test_views.py @@ -375,10 +375,10 @@ def test_particular_challenge_host_team_for_challenge_host_does_not_exist( "pk": self.challenge_host.pk, }, ) - expected = {"error": "ChallengeHostTeam does not exist"} + expected = {"detail": "ChallengeHostTeam " + str(self.challenge_host_team.pk + 1) + " does not exist"} response = self.client.get(self.url, {}) - self.assertEqual(response.data, expected) - self.assertEqual(response.status_code, status.HTTP_406_NOT_ACCEPTABLE) + self.assertDictEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class UpdateParticularChallengeHost(BaseAPITestClass): @@ -430,6 +430,76 @@ def test_particular_challenge_host_update_with_no_data(self): class DeleteParticularChallengeHost(BaseAPITestClass): def setUp(self): super(DeleteParticularChallengeHost, self).setUp() + self.challenge_host = ChallengeHost.objects.create( + user=self.user, + team_name=self.challenge_host_team, + status=ChallengeHost.ACCEPTED, + permissions=ChallengeHost.ADMIN, + ) + + self.user2 = User.objects.create( + username="user2", + email="user2@platform.com", + password="user2_password", + ) + + self.challenge_host2 = ChallengeHost.objects.create( + user=self.user2, + team_name=self.challenge_host_team, + status=ChallengeHost.ACCEPTED, + permissions=ChallengeHost.ACCEPTED, + ) + + self.user3 = User.objects.create( + username="user3", + email="user3@platform.com", + password="user3_password", + ) + + EmailAddress.objects.create( + user=self.user3, + email="user3@platform.com", + primary=True, + verified=True, + ) + + self.challenge_host3 = ChallengeHost.objects.create( + user=self.user3, + team_name=self.challenge_host_team, + status=ChallengeHost.ACCEPTED, + permissions=ChallengeHost.ACCEPTED, + ) + + def test_delete_challenge_host_when_challenge_host_does_not_exist(self): + self.url = reverse_lazy( + "hosts:get_challenge_host_details", + kwargs={ + "challenge_host_team_pk": self.challenge_host_team.pk, + "pk": self.challenge_host3.pk + 1, + }, + ) + + expected = {"detail": "ChallengeHost " + str(self.challenge_host3.pk + 1) + " does not exist"} + + response = self.client.delete(self.url, {}) + self.assertDictEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_delete_challenge_host_when_challenge_host_team_does_not_exist(self): + self.url = reverse_lazy( + "hosts:get_challenge_host_details", + kwargs={ + "challenge_host_team_pk": self.challenge_host_team.pk + 1, + "pk": self.challenge_host2.pk, + }, + ) + + expected = {"detail": "ChallengeHostTeam " + str(self.challenge_host_team.pk + 1) + " does not exist"} + response = self.client.delete(self.url, {}) + self.assertDictEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_delete_challenge_host_when_admin_tries_to_delete_himself(self): self.url = reverse_lazy( "hosts:get_challenge_host_details", kwargs={ @@ -438,7 +508,55 @@ def setUp(self): }, ) + expected = { + "error": "You are not allowed to remove yourself since you are the team admin. Please delete the team if you want to do so!" # noqa: ignore=E501 + } + + response = self.client.delete(self.url, {}) + self.assertEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_406_NOT_ACCEPTABLE) + + def test_delete_challenge_host_when_challenge_host_does_not_have_permission( + self + ): + + self.url = reverse_lazy( + "hosts:get_challenge_host_details", + kwargs={ + "challenge_host_team_pk": self.challenge_host_team.pk, + "pk": self.challenge_host2.pk, + }, + ) + + self.client.force_authenticate(user=self.user3) + + expected = { + "error": "Sorry, you do not have permissions to remove this challenge host" + } + + response = self.client.delete(self.url, {}) + self.assertEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_delete_challenge_host_success(self): + self.url = reverse_lazy( + "hosts:get_challenge_host_details", + kwargs={ + "challenge_host_team_pk": self.challenge_host_team.pk, + "pk": self.challenge_host2.pk, + }, + ) + response = self.client.delete(self.url, {}) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + def test_particular_challenge_host_delete(self): + self.url = reverse_lazy( + "hosts:get_challenge_host_details", + kwargs={ + "challenge_host_team_pk": self.challenge_host_team.pk, + "pk": self.challenge_host2.pk, + }, + ) response = self.client.delete(self.url, {}) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/tests/unit/participants/test_views.py b/tests/unit/participants/test_views.py index f03a8452a7..0e78dfe761 100644 --- a/tests/unit/participants/test_views.py +++ b/tests/unit/participants/test_views.py @@ -79,11 +79,13 @@ def test_get_challenge(self): "team_url": self.participant_team.team_url, "members": [ { + "id": self.participant.pk, "member_name": self.participant.user.username, "status": self.participant.status, "member_id": self.participant.user.id, }, { + "id": self.participant2.pk, "member_name": self.participant2.user.username, "status": self.participant2.status, "member_id": self.participant2.user.id, @@ -167,11 +169,13 @@ def test_get_particular_participant_team(self): "team_url": self.participant_team.team_url, "members": [ { + "id": self.participant.pk, "member_name": self.participant.user.username, "status": self.participant.status, "member_id": self.participant.user.id, }, { + "id": self.participant2.pk, "member_name": self.participant2.user.username, "status": self.participant2.status, "member_id": self.participant2.user.id,