From 6cf3c5727e367ae300b6525086b08069accb7afb Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 4 Feb 2016 20:23:03 +0100 Subject: [PATCH] Fixing serializers validation for duplicated names in priorities, severities and issue types --- taiga/projects/milestones/serializers.py | 21 +----- taiga/projects/mixins/serializers.py | 40 +++++++++++ taiga/projects/serializers.py | 85 +++--------------------- tests/unit/test_serializer_mixins.py | 44 ++++++++++++ 4 files changed, 94 insertions(+), 96 deletions(-) create mode 100644 taiga/projects/mixins/serializers.py create mode 100644 tests/unit/test_serializer_mixins.py diff --git a/taiga/projects/milestones/serializers.py b/taiga/projects/milestones/serializers.py index 2e6d95ac..191b2234 100644 --- a/taiga/projects/milestones/serializers.py +++ b/taiga/projects/milestones/serializers.py @@ -21,12 +21,12 @@ from taiga.base.api import serializers from taiga.base.utils import json from taiga.projects.notifications.mixins import WatchedResourceModelSerializer from taiga.projects.notifications.validators import WatchersValidator - +from taiga.projects.mixins.serializers import ValidateDuplicatedNameInProjectMixin from ..userstories.serializers import UserStoryListSerializer from . import models -class MilestoneSerializer(WatchersValidator, WatchedResourceModelSerializer, serializers.ModelSerializer): +class MilestoneSerializer(WatchersValidator, WatchedResourceModelSerializer, ValidateDuplicatedNameInProjectMixin): user_stories = UserStoryListSerializer(many=True, required=False, read_only=True) total_points = serializers.SerializerMethodField("get_total_points") closed_points = serializers.SerializerMethodField("get_closed_points") @@ -40,20 +40,3 @@ class MilestoneSerializer(WatchersValidator, WatchedResourceModelSerializer, ser def get_closed_points(self, obj): return sum(obj.closed_points.values()) - - def validate_name(self, attrs, source): - """ - Check the milestone name is not duplicated in the project on creation - """ - qs = None - # If the milestone exists: - if self.object and attrs.get("name", None): - qs = models.Milestone.objects.filter(project=self.object.project, name=attrs[source]).exclude(pk=self.object.pk) - - if not self.object and attrs.get("project", None) and attrs.get("name", None): - qs = models.Milestone.objects.filter(project=attrs["project"], name=attrs[source]) - - if qs and qs.exists(): - raise serializers.ValidationError(_("Name duplicated for the project")) - - return attrs diff --git a/taiga/projects/mixins/serializers.py b/taiga/projects/mixins/serializers.py new file mode 100644 index 00000000..edaa0c1a --- /dev/null +++ b/taiga/projects/mixins/serializers.py @@ -0,0 +1,40 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from taiga.base.api import serializers + +from django.utils.translation import ugettext as _ + +class ValidateDuplicatedNameInProjectMixin(serializers.ModelSerializer): + + def validate_name(self, attrs, source): + """ + Check the points name is not duplicated in the project on creation + """ + model = self.opts.model + qs = None + # If the object exists: + if self.object and attrs.get(source, None): + qs = model.objects.filter(project=self.object.project, name=attrs[source]).exclude(id=self.object.id) + + if not self.object and attrs.get("project", None) and attrs.get(source, None): + qs = model.objects.filter(project=attrs["project"], name=attrs[source]) + + if qs and qs.exists(): + raise serializers.ValidationError(_("Name duplicated for the project")) + + return attrs diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index b60c474f..784a5a8a 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -34,6 +34,7 @@ from taiga.users.validators import RoleExistsValidator from taiga.permissions.service import get_user_project_permissions from taiga.permissions.service import is_project_owner +from taiga.projects.mixins.serializers import ValidateDuplicatedNameInProjectMixin from . import models from . import services @@ -49,53 +50,17 @@ from .likes.mixins.serializers import FanResourceSerializerMixin ## Custom values for selectors ###################################################### -class PointsSerializer(serializers.ModelSerializer): +class PointsSerializer(ValidateDuplicatedNameInProjectMixin): class Meta: model = models.Points i18n_fields = ("name",) - def validate_name(self, attrs, source): - """ - Check the points name is not duplicated in the project on creation - """ - qs = None - # If the user story status exists: - if self.object and attrs.get("name", None): - qs = models.Points.objects.filter(project=self.object.project, name=attrs[source]) - if not self.object and attrs.get("project", None) and attrs.get("name", None): - qs = models.Points.objects.filter(project=attrs["project"], name=attrs[source]) - - if qs and qs.exists(): - raise serializers.ValidationError(_("Name duplicated for the project")) - - return attrs - - -class UserStoryStatusSerializer(serializers.ModelSerializer): +class UserStoryStatusSerializer(ValidateDuplicatedNameInProjectMixin): class Meta: model = models.UserStoryStatus i18n_fields = ("name",) - def validate_name(self, attrs, source): - """ - Check the status name is not duplicated in the project on creation - """ - qs = None - # If the user story status exists: - if self.object and attrs.get("name", None): - qs = models.UserStoryStatus.objects.filter(project=self.object.project, - name=attrs[source]) - - if not self.object and attrs.get("project", None) and attrs.get("name", None): - qs = models.UserStoryStatus.objects.filter(project=attrs["project"], - name=attrs[source]) - - if qs and qs.exists(): - raise serializers.ValidationError(_("Name duplicated for the project")) - - return attrs - class BasicUserStoryStatusSerializer(serializers.ModelSerializer): class Meta: @@ -104,28 +69,11 @@ class BasicUserStoryStatusSerializer(serializers.ModelSerializer): fields = ("name", "color") -class TaskStatusSerializer(serializers.ModelSerializer): +class TaskStatusSerializer(ValidateDuplicatedNameInProjectMixin): class Meta: model = models.TaskStatus i18n_fields = ("name",) - def validate_name(self, attrs, source): - """ - Check the task name is not duplicated in the project on creation - """ - qs = None - # If the user story status exists: - if self.object and attrs.get("name", None): - qs = models.TaskStatus.objects.filter(project=self.object.project, name=attrs[source]) - - if not self.object and attrs.get("project", None) and attrs.get("name", None): - qs = models.TaskStatus.objects.filter(project=attrs["project"], name=attrs[source]) - - if qs and qs.exists(): - raise serializers.ValidationError(_("Name duplicated for the project")) - - return attrs - class BasicTaskStatusSerializerSerializer(serializers.ModelSerializer): @@ -135,40 +83,23 @@ class BasicTaskStatusSerializerSerializer(serializers.ModelSerializer): fields = ("name", "color") -class SeveritySerializer(serializers.ModelSerializer): +class SeveritySerializer(ValidateDuplicatedNameInProjectMixin): class Meta: model = models.Severity i18n_fields = ("name",) -class PrioritySerializer(serializers.ModelSerializer): +class PrioritySerializer(ValidateDuplicatedNameInProjectMixin): class Meta: model = models.Priority i18n_fields = ("name",) -class IssueStatusSerializer(serializers.ModelSerializer): +class IssueStatusSerializer(ValidateDuplicatedNameInProjectMixin): class Meta: model = models.IssueStatus i18n_fields = ("name",) - def validate_name(self, attrs, source): - """ - Check the issue name is not duplicated in the project on creation - """ - qs = None - # If the user story status exists: - if self.object and attrs.get("name", None): - qs = models.IssueStatus.objects.filter(project=self.object.project, name=attrs[source]) - - if not self.object and attrs.get("project", None) and attrs.get("name", None): - qs = models.IssueStatus.objects.filter(project=attrs["project"], name=attrs[source]) - - if qs and qs.exists(): - raise serializers.ValidationError(_("Name duplicated for the project")) - - return attrs - class BasicIssueStatusSerializer(serializers.ModelSerializer): class Meta: @@ -177,7 +108,7 @@ class BasicIssueStatusSerializer(serializers.ModelSerializer): fields = ("name", "color") -class IssueTypeSerializer(serializers.ModelSerializer): +class IssueTypeSerializer(ValidateDuplicatedNameInProjectMixin): class Meta: model = models.IssueType i18n_fields = ("name",) diff --git a/tests/unit/test_serializer_mixins.py b/tests/unit/test_serializer_mixins.py new file mode 100644 index 00000000..e9f94e0c --- /dev/null +++ b/tests/unit/test_serializer_mixins.py @@ -0,0 +1,44 @@ +import pytest + +from .. import factories as f +from django.db import models +from taiga.projects.mixins.serializers import ValidateDuplicatedNameInProjectMixin +from taiga.projects.models import Project + +pytestmark = pytest.mark.django_db(transaction=True) + +import factory + + +class TestingProjectModel(models.Model): + pass + +class TestingModelWithNameAttribute(models.Model): + name = models.CharField(max_length=255, null=False, blank=False) + project = models.ForeignKey(TestingProjectModel, null=False, blank=False) + + +class TestingSerializer(ValidateDuplicatedNameInProjectMixin): + class Meta: + model = TestingModelWithNameAttribute + + +def test_duplicated_name_validation(): + project = TestingProjectModel.objects.create() + instance_1 = TestingModelWithNameAttribute.objects.create(name="1", project=project) + instance_2 = TestingModelWithNameAttribute.objects.create(name="2", project=project) + + # No duplicated_name + serializer = TestingSerializer(data={"name": "3", "project": project.id}) + + assert serializer.is_valid() + + # Create duplicated_name + serializer = TestingSerializer(data={"name": "1", "project": project.id}) + + assert not serializer.is_valid() + + # Update name to existing one + serializer = TestingSerializer(data={"id": instance_2.id, "name": "1","project": project.id}) + + assert not serializer.is_valid()