diff --git a/greenmine/base/filters.py b/greenmine/base/filters.py index 5675338f..cd299c9f 100644 --- a/greenmine/base/filters.py +++ b/greenmine/base/filters.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- +from django.db.models import Q + from rest_framework import filters + class QueryParamsFilterMixin(object): _special_values_dict = { 'true': True, @@ -44,6 +47,6 @@ class IsProjectMemberFilterBackend(FilterBackend): user = request.user if user.is_authenticated(): - queryset = queryset.filter(project__members=request.user) - - return queryset + queryset = queryset.filter(Q(project__members=request.user) | + Q(project__owner=request.user)) + return queryset.distinct() diff --git a/greenmine/projects/milestones/api.py b/greenmine/projects/milestones/api.py index 06087834..1cb8d194 100644 --- a/greenmine/projects/milestones/api.py +++ b/greenmine/projects/milestones/api.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- +from django.utils.translation import ugettext_lazy as _ + from rest_framework.permissions import IsAuthenticated +from rest_framework.exceptions import ParseError from greenmine.base import filters from greenmine.base.api import ModelCrudViewSet @@ -26,4 +29,6 @@ class MilestoneViewSet(NotificationSenderMixin, ModelCrudViewSet): if not obj.id: obj.owner = self.request.user - + if (obj.project.owner != self.request.user and + obj.project.memberships.filter(user=self.request.user).count() == 0): + raise ParseError(detail=_("You must not add a new milestone to this project.")) diff --git a/greenmine/projects/milestones/models.py b/greenmine/projects/milestones/models.py index e5ddcb87..307b9d3e 100644 --- a/greenmine/projects/milestones/models.py +++ b/greenmine/projects/milestones/models.py @@ -99,39 +99,45 @@ class Milestone(models.Model, WatchedMixin): @property def client_increment_points(self): - user_stories = UserStory.objects.filter( - created_date__gte=self.estimated_start, - created_date__lt=self.estimated_finish, - project_id=self.project_id, - client_requirement=True, - team_requirement=False - ) + user_stories = UserStory.objects.none() + if self.estimated_start and self.estimated_finish: + user_stories = UserStory.objects.filter( + created_date__gte=self.estimated_start, + created_date__lt=self.estimated_finish, + project_id=self.project_id, + client_requirement=True, + team_requirement=False + ) client_increment = self._get_user_stories_points(user_stories) shared_increment = {key: value/2 for key, value in self.shared_increment_points.items()} return self._dict_sum(client_increment, shared_increment) @property def team_increment_points(self): - user_stories = UserStory.objects.filter( - created_date__gte=self.estimated_start, - created_date__lt=self.estimated_finish, - project_id=self.project_id, - client_requirement=False, - team_requirement=True - ) + user_stories = UserStory.objects.none() + if self.estimated_start and self.estimated_finish: + user_stories = UserStory.objects.filter( + created_date__gte=self.estimated_start, + created_date__lt=self.estimated_finish, + project_id=self.project_id, + client_requirement=False, + team_requirement=True + ) team_increment = self._get_user_stories_points(user_stories) shared_increment = {key: value/2 for key, value in self.shared_increment_points.items()} return self._dict_sum(team_increment, shared_increment) @property def shared_increment_points(self): - user_stories = UserStory.objects.filter( - created_date__gte=self.estimated_start, - created_date__lt=self.estimated_finish, - project_id=self.project_id, - client_requirement=True, - team_requirement=True - ) + user_stories = UserStory.objects.none() + if self.estimated_start and self.estimated_finish: + user_stories = UserStory.objects.filter( + created_date__gte=self.estimated_start, + created_date__lt=self.estimated_finish, + project_id=self.project_id, + client_requirement=True, + team_requirement=True + ) return self._get_user_stories_points(user_stories) def _get_watchers_by_role(self): diff --git a/greenmine/projects/milestones/tests/__init__.py b/greenmine/projects/milestones/tests/__init__.py new file mode 100644 index 00000000..dc9d95dc --- /dev/null +++ b/greenmine/projects/milestones/tests/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from django.db.models.loading import get_model + + +def create_milestone(id, owner, project, save=True): + model = get_model("milestones", "Milestone") + + instance = model( + name="Milestone {0}".format(id), + project=project, + owner=owner + ) + + if save: + instance.save() + return instance diff --git a/greenmine/projects/milestones/tests/tests_api.py b/greenmine/projects/milestones/tests/tests_api.py new file mode 100644 index 00000000..4f1136fa --- /dev/null +++ b/greenmine/projects/milestones/tests/tests_api.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- + +import json + +from django import test +from django.core import mail +from django.core.urlresolvers import reverse + +from greenmine.base.users.tests import create_user +from greenmine.projects.tests import create_project, add_membership +from greenmine.projects.milestones.models import Milestone + +from . import create_milestone + + +class MilestonesTestCase(test.TestCase): + fixtures = ["initial_role.json", ] + + def setUp(self): + self.user1 = create_user(1) + self.user2 = create_user(2) + self.user3 = create_user(3) + self.user4 = create_user(4) + + self.project1 = create_project(1, self.user1) + add_membership(self.project1, self.user2, "dev") + add_membership(self.project1, self.user3, "dev") + + self.project2 = create_project(2, self.user4) + + self.milestone1 = create_milestone(1, self.user2, self.project1) + self.milestone2 = create_milestone(2, self.user2, self.project1) + + def test_list_milestones_by_anon(self): + response = self.client.get(reverse("milestones-list")) + self.assertEqual(response.status_code, 401) + + def test_list_milestones_by_project_owner(self): + response = self.client.login(username=self.user1.username, + password=self.user1.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-list")) + self.assertEqual(response.status_code, 200) + milestones_list = response.data + self.assertEqual(len(milestones_list), 2) + self.client.logout() + + + def test_list_milestones_by_owner(self): + response = self.client.login(username=self.user2.username, + password=self.user2.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-list")) + self.assertEqual(response.status_code, 200) + milestones_list = response.data + self.assertEqual(len(milestones_list), 2) + self.client.logout() + + def test_list_milestones_by_membership(self): + response = self.client.login(username=self.user3.username, + password=self.user3.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-list")) + self.assertEqual(response.status_code, 200) + milestones_list = response.data + self.assertEqual(len(milestones_list), 2) + self.client.logout() + + def test_list_milestones_by_no_membership(self): + response = self.client.login(username=self.user4.username, + password=self.user4.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-list")) + self.assertEqual(response.status_code, 200) + milestones_list = response.data + self.assertEqual(len(milestones_list), 0) + self.client.logout() + + def test_view_milestone_by_anon(self): + response = self.client.get(reverse("milestones-detail", args=(self.milestone1.id,))) + self.assertEqual(response.status_code, 401) + + def test_view_milestone_by_project_owner(self): + response = self.client.login(username=self.user1.username, + password=self.user1.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-detail", args=(self.milestone1.id,))) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_view_milestone_by_owner(self): + response = self.client.login(username=self.user2.username, + password=self.user2.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-detail", args=(self.milestone1.id,))) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_view_milestone_by_membership(self): + response = self.client.login(username=self.user3.username, + password=self.user3.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-detail", args=(self.milestone1.id,))) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_view_milestone_by_no_membership(self): + response = self.client.login(username=self.user4.username, + password=self.user4.username) + self.assertTrue(response) + response = self.client.get(reverse("milestones-detail", args=(self.milestone1.id,))) + self.assertEqual(response.status_code, 404) + self.client.logout() + + + def test_create_milestone_by_anon(self): + data = { + "name": "Test Milestone", + "project": self.project1.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.post( + reverse("milestones-list"), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 401) + self.assertEqual(Milestone.objects.all().count(), 2) + + def test_create_milestone_by_project_owner(self): + data = { + "name": "Test Milestone", + "project": self.project1.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertEqual(len(mail.outbox), 0) + response = self.client.login(username=self.user1.username, + password=self.user1.username) + self.assertTrue(response) + response = self.client.post( + reverse("milestones-list"), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 201) + self.assertEqual(Milestone.objects.all().count(), 3) + self.assertEqual(len(mail.outbox), 1) + self.client.logout() + + def test_create_milestone_by_membership(self): + data = { + "name": "Test Milestone", + "project": self.project1.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertEqual(len(mail.outbox), 0) + response = self.client.login(username=self.user2.username, + password=self.user2.username) + self.assertTrue(response) + response = self.client.post( + reverse("milestones-list"), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 201) + self.assertEqual(Milestone.objects.all().count(), 3) + self.assertEqual(len(mail.outbox), 1) + self.client.logout() + + def test_create_milestone_by_membership_with_wron_project(self): + data = { + "name": "Test Milestone", + "project": self.project2.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user2.username, + password=self.user2.username) + self.assertTrue(response) + response = self.client.post( + reverse("milestones-list"), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 400) + self.assertEqual(Milestone.objects.all().count(), 2) + self.client.logout() + + def test_create_milestone_by_no_membership(self): + data = { + "name": "Test Milestone", + "project": self.project1.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertEqual(len(mail.outbox), 0) + response = self.client.login(username=self.user4.username, + password=self.user4.username) + self.assertTrue(response) + response = self.client.post( + reverse("milestones-list"), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 400) + self.assertEqual(Milestone.objects.all().count(), 2) + self.client.logout() + + def test_edit_milestone_by_anon(self): + data = { + "name": "Edited test milestone", + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertNotEqual(data["name"], self.milestone1.name) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 401) + self.assertEqual(Milestone.objects.all().count(), 2) + + def test_edit_milestone_by_project_owner(self): + data = { + "name": "Modified milestone name", + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertNotEqual(data["name"], self.milestone1.name) + self.assertEqual(len(mail.outbox), 0) + response = self.client.login(username=self.user1.username, + password=self.user1.username) + self.assertTrue(response) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 200) + self.assertEqual(data["name"], response.data["name"]) + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertEqual(len(mail.outbox), 1) + self.client.logout() + + def test_edit_milestone_by_project_owner_with_wron_project(self): + data = { + "project": self.project2.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user1.username, + password=self.user1.username) + self.assertTrue(response) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 400) + self.assertEqual(Milestone.objects.all().count(), 2) + self.client.logout() + + def test_edit_milestone_by_owner(self): + data = { + "name": "Modified milestone name", + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertNotEqual(data["name"], self.milestone1.name) + self.assertEqual(len(mail.outbox), 0) + response = self.client.login(username=self.user2.username, + password=self.user2.username) + self.assertTrue(response) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 200) + self.assertEqual(data["name"], response.data["name"]) + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertEqual(len(mail.outbox), 1) + self.client.logout() + + def test_edit_milestone_by_owner_with_wron_project(self): + data = { + "project": self.project2.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user2.username, + password=self.user2.username) + self.assertTrue(response) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 400) + self.assertEqual(Milestone.objects.all().count(), 2) + self.client.logout() + + def test_edit_milestone_by_membership(self): + data = { + "name": "Modified milestone name", + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertNotEqual(data["name"], self.milestone1.name) + self.assertEqual(len(mail.outbox), 0) + response = self.client.login(username=self.user3.username, + password=self.user3.username) + self.assertTrue(response) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 200) + self.assertEqual(data["name"], response.data["name"]) + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertEqual(len(mail.outbox), 1) + self.client.logout() + + def test_edit_milestone_by_membership_with_wron_project(self): + data = { + "project": self.project2.id + } + + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user3.username, + password=self.user3.username) + self.assertTrue(response) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 400) + self.assertEqual(Milestone.objects.all().count(), 2) + self.client.logout() + + def test_edit_milestone_by_no_membership(self): + data = { + "name": "Modified milestone name", + } + + self.assertEqual(Milestone.objects.all().count(), 2) + self.assertNotEqual(data["name"], self.milestone1.name) + response = self.client.login(username=self.user4.username, + password=self.user4.username) + self.assertTrue(response) + response = self.client.patch( + reverse("milestones-detail", args=(self.milestone1.id,)), + json.dumps(data), + content_type="application/json") + self.assertEqual(response.status_code, 404) + self.assertEqual(Milestone.objects.all().count(), 2) + self.client.logout() + + def test_delete_milestone_by_ano(self): + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.delete( + reverse("milestones-detail", args=(self.milestone1.id,)) + ) + self.assertEqual(response.status_code, 401) + self.assertEqual(Milestone.objects.all().count(), 2) + + def test_delete_milestone_by_project_owner(self): + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user1.username, + password=self.user1.username) + self.assertTrue(response) + response = self.client.delete( + reverse("milestones-detail", args=(self.milestone1.id,)) + ) + self.assertEqual(response.status_code, 204) + self.assertEqual(Milestone.objects.all().count(), 1) + self.client.logout() + + def test_delete_milestone_by_owner(self): + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user2.username, + password=self.user2.username) + self.assertTrue(response) + response = self.client.delete( + reverse("milestones-detail", args=(self.milestone1.id,)) + ) + self.assertEqual(response.status_code, 204) + self.assertEqual(Milestone.objects.all().count(), 1) + self.client.logout() + + def test_delete_milestone_by_membership(self): + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user3.username, + password=self.user3.username) + self.assertTrue(response) + response = self.client.delete( + reverse("milestones-detail", args=(self.milestone1.id,)) + ) + self.assertEqual(response.status_code, 204) + self.assertEqual(Milestone.objects.all().count(), 1) + self.client.logout() + + def test_delete_milestone_by_no_membership(self): + self.assertEqual(Milestone.objects.all().count(), 2) + response = self.client.login(username=self.user4.username, + password=self.user4.username) + self.assertTrue(response) + response = self.client.delete( + reverse("milestones-detail", args=(self.milestone1.id,)) + ) + self.assertEqual(response.status_code, 404) + self.assertEqual(Milestone.objects.all().count(), 2) + self.client.logout() + diff --git a/greenmine/projects/userstories/api.py b/greenmine/projects/userstories/api.py index bc444009..fb49680b 100644 --- a/greenmine/projects/userstories/api.py +++ b/greenmine/projects/userstories/api.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- +from django.utils.translation import ugettext_lazy as _ from django.contrib.contenttypes.models import ContentType from rest_framework.permissions import IsAuthenticated +from rest_framework.exceptions import ParseError from greenmine.base import filters from greenmine.base.api import ModelCrudViewSet @@ -53,6 +55,13 @@ class UserStoryViewSet(NotificationSenderMixin, ModelCrudViewSet): if not obj.id: obj.owner = self.request.user + if (obj.project.owner != self.request.user and + obj.project.memberships.filter(user=self.request.user).count() == 0): + raise ParseError(detail=_("You must not add a new user story to this project.")) + + if obj.milestone and obj.milestone.project != obj.project: + raise ParseError(detail=_("You must not add a new user story to this milestone.")) + def post_save(self, obj, created=False): with reversion.create_revision(): if "comment" in self.request.DATA: