378 lines
13 KiB
Python
378 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
|
|
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
|
|
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
|
|
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
from django.db.models import Q
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from taiga.base.api import serializers
|
|
from taiga.base.api import validators
|
|
from taiga.base.api.fields import validate_user_email_allowed_domains, InvalidEmailValidationError
|
|
from taiga.base.exceptions import ValidationError
|
|
from taiga.base.fields import JSONField
|
|
from taiga.base.fields import PgArrayField
|
|
from taiga.users.models import User, Role
|
|
|
|
|
|
from .tagging.fields import TagsField
|
|
|
|
from . import models
|
|
from . import services
|
|
|
|
|
|
class DuplicatedNameInProjectValidator:
|
|
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 ValidationError(_("Name duplicated for the project"))
|
|
|
|
return attrs
|
|
|
|
|
|
class ProjectExistsValidator:
|
|
def validate_project_id(self, attrs, source):
|
|
value = attrs[source]
|
|
if not models.Project.objects.filter(pk=value).exists():
|
|
msg = _("There's no project with that id")
|
|
raise ValidationError(msg)
|
|
return attrs
|
|
|
|
|
|
######################################################
|
|
# Custom values for selectors
|
|
######################################################
|
|
|
|
class EpicStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.EpicStatus
|
|
|
|
|
|
class UserStoryStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.UserStoryStatus
|
|
|
|
|
|
class PointsValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.Points
|
|
|
|
|
|
class UserStoryDueDateValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.UserStoryDueDate
|
|
|
|
|
|
class TaskStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.TaskStatus
|
|
|
|
|
|
class TaskDueDateValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.TaskDueDate
|
|
|
|
|
|
class SeverityValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.Severity
|
|
|
|
|
|
class PriorityValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.Priority
|
|
|
|
|
|
class IssueStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.IssueStatus
|
|
|
|
|
|
class IssueTypeValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.IssueType
|
|
|
|
|
|
class IssueDueDateValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
|
|
class Meta:
|
|
model = models.IssueDueDate
|
|
|
|
|
|
class DueDatesCreationValidator(ProjectExistsValidator, validators.Validator):
|
|
project_id = serializers.IntegerField()
|
|
|
|
|
|
######################################################
|
|
# Members
|
|
######################################################
|
|
|
|
class MembershipValidator(validators.ModelValidator):
|
|
username = serializers.CharField(required=True)
|
|
|
|
class Meta:
|
|
model = models.Membership
|
|
read_only_fields = ("user", "email")
|
|
|
|
def restore_object(self, attrs, instance=None):
|
|
username = attrs.pop("username", None)
|
|
obj = super(MembershipValidator, self).restore_object(attrs, instance=instance)
|
|
obj.username = username
|
|
return obj
|
|
|
|
def _validate_member_doesnt_exist(self, attrs, email):
|
|
project = attrs.get("project", None if self.object is None else self.object.project)
|
|
if project is None:
|
|
return attrs
|
|
|
|
qs = models.Membership.objects.all()
|
|
|
|
# If self.object is not None, the serializer is in update
|
|
# mode, and for it, it should exclude self.
|
|
if self.object:
|
|
qs = qs.exclude(pk=self.object.pk)
|
|
|
|
qs = qs.filter(Q(project_id=project.id, user__email=email) |
|
|
Q(project_id=project.id, email=email))
|
|
|
|
if qs.count() > 0:
|
|
raise ValidationError(_("The user yet exists in the project"))
|
|
|
|
def validate_role(self, attrs, source):
|
|
project = attrs.get("project", None if self.object is None else self.object.project)
|
|
if project is None:
|
|
return attrs
|
|
|
|
role = attrs[source]
|
|
|
|
if project.roles.filter(id=role.id).count() == 0:
|
|
raise ValidationError(_("Invalid role for the project"))
|
|
|
|
return attrs
|
|
|
|
def validate_username(self, attrs, source):
|
|
username = attrs.get(source, None)
|
|
try:
|
|
validate_user_email_allowed_domains(username)
|
|
|
|
except ValidationError:
|
|
# If the validation comes from a request let's check the user is a valid contact
|
|
request = self.context.get("request", None)
|
|
if request is not None and request.user.is_authenticated():
|
|
valid_usernames = request.user.contacts_visible_by_user(request.user).values_list("username", flat=True)
|
|
if username not in valid_usernames:
|
|
raise ValidationError(_("The user must be a valid contact"))
|
|
|
|
user = User.objects.filter(Q(username=username) | Q(email=username)).first()
|
|
if user is not None:
|
|
email = user.email
|
|
self.user = user
|
|
|
|
else:
|
|
email = username
|
|
|
|
self.email = email
|
|
self._validate_member_doesnt_exist(attrs, email)
|
|
return attrs
|
|
|
|
def validate_is_admin(self, attrs, source):
|
|
project = attrs.get("project", None if self.object is None else self.object.project)
|
|
if project is None:
|
|
return attrs
|
|
|
|
if (self.object and self.object.user):
|
|
if self.object.user.id == project.owner_id and not attrs[source]:
|
|
raise ValidationError(_("The project owner must be admin."))
|
|
|
|
if not services.project_has_valid_admins(project, exclude_user=self.object.user):
|
|
raise ValidationError(
|
|
_("At least one user must be an active admin for this project.")
|
|
)
|
|
|
|
return attrs
|
|
|
|
def is_valid(self):
|
|
errors = super().is_valid()
|
|
if hasattr(self, "email") and self.object is not None:
|
|
self.object.email = self.email
|
|
|
|
if hasattr(self, "user") and self.object is not None:
|
|
self.object.user = self.user
|
|
|
|
return errors
|
|
|
|
|
|
class _MemberBulkValidator(validators.Validator):
|
|
username = serializers.CharField()
|
|
role_id = serializers.IntegerField()
|
|
|
|
def validate_username(self, attrs, source):
|
|
username = attrs.get(source)
|
|
try:
|
|
validate_user_email_allowed_domains(username)
|
|
except InvalidEmailValidationError:
|
|
# If the validation comes from a request let's check the user is a valid contact
|
|
request = self.context.get("request", None)
|
|
if request is not None and request.user.is_authenticated():
|
|
valid_usernames = set(request.user.contacts_visible_by_user(request.user).values_list("username", flat=True))
|
|
if username not in valid_usernames:
|
|
raise ValidationError(_("The user must be a valid contact"))
|
|
|
|
return attrs
|
|
|
|
|
|
class MembersBulkValidator(ProjectExistsValidator, validators.Validator):
|
|
project_id = serializers.IntegerField()
|
|
bulk_memberships = _MemberBulkValidator(many=True)
|
|
invitation_extra_text = serializers.CharField(required=False, max_length=255)
|
|
|
|
def validate_bulk_memberships(self, attrs, source):
|
|
project_id = attrs["project_id"]
|
|
role_ids = [r["role_id"] for r in attrs["bulk_memberships"]]
|
|
|
|
if Role.objects.filter(project_id=project_id, id__in=role_ids).count() != len(set(role_ids)):
|
|
raise ValidationError(_("Invalid role ids. All roles must belong to the same project."))
|
|
|
|
return attrs
|
|
|
|
|
|
######################################################
|
|
# Projects
|
|
######################################################
|
|
|
|
class ProjectValidator(validators.ModelValidator):
|
|
anon_permissions = PgArrayField(required=False)
|
|
public_permissions = PgArrayField(required=False)
|
|
tags = TagsField(default=[], required=False)
|
|
|
|
class Meta:
|
|
model = models.Project
|
|
read_only_fields = ("created_date", "modified_date", "slug", "blocked_code", "owner")
|
|
|
|
|
|
######################################################
|
|
# Project Templates
|
|
######################################################
|
|
|
|
class ProjectTemplateValidator(validators.ModelValidator):
|
|
default_options = JSONField(required=False, label=_("Default options"))
|
|
us_statuses = JSONField(required=False, label=_("User story's statuses"))
|
|
points = JSONField(required=False, label=_("Points"))
|
|
task_statuses = JSONField(required=False, label=_("Task's statuses"))
|
|
issue_statuses = JSONField(required=False, label=_("Issue's statuses"))
|
|
issue_types = JSONField(required=False, label=_("Issue's types"))
|
|
priorities = JSONField(required=False, label=_("Priorities"))
|
|
severities = JSONField(required=False, label=_("Severities"))
|
|
roles = JSONField(required=False, label=_("Roles"))
|
|
|
|
class Meta:
|
|
model = models.ProjectTemplate
|
|
read_only_fields = ("created_date", "modified_date")
|
|
|
|
|
|
######################################################
|
|
# Project order bulk validators
|
|
######################################################
|
|
|
|
class UpdateProjectOrderBulkValidator(ProjectExistsValidator, validators.Validator):
|
|
project_id = serializers.IntegerField()
|
|
order = serializers.IntegerField()
|
|
|
|
|
|
######################################################
|
|
# Project duplication validator
|
|
######################################################
|
|
|
|
|
|
class DuplicateProjectMemberValidator(validators.Validator):
|
|
id = serializers.CharField()
|
|
|
|
|
|
class DuplicateProjectValidator(validators.Validator):
|
|
name = serializers.CharField()
|
|
description = serializers.CharField()
|
|
is_private = serializers.BooleanField()
|
|
users = DuplicateProjectMemberValidator(many=True)
|
|
|
|
|
|
class GameValidator(validators.ModelValidator):
|
|
class Meta:
|
|
model = models.Game
|
|
|
|
def validate_roles(self, attrs, source):
|
|
project = attrs.get("project", None if self.object is None else self.object.project)
|
|
if project is None:
|
|
return attrs
|
|
|
|
roles = attrs[source]
|
|
if not isinstance(roles, list):
|
|
raise ValidationError(_("Invalid roles format"))
|
|
|
|
for role in roles:
|
|
if "id" not in role or "name" not in role:
|
|
raise ValidationError(_("Invalid role format"))
|
|
|
|
if project.roles.filter(id=role['id']).count() == 0:
|
|
raise ValidationError(_("Invalid role for the project"))
|
|
|
|
return attrs
|
|
|
|
def validate_scales(self, attrs, source):
|
|
scales = attrs[source]
|
|
if not isinstance(scales, list):
|
|
raise ValidationError(_("Invalid scales format"))
|
|
|
|
for scale in scales:
|
|
if "id" not in scale or "name" not in scale:
|
|
raise ValidationError(_("Invalid scale format"))
|
|
|
|
return attrs
|
|
|
|
def validate_userstories(self, attrs, source):
|
|
project = attrs.get("project", None if self.object is None else self.object.project)
|
|
if project is None:
|
|
return attrs
|
|
|
|
userstories = attrs[source]
|
|
if not isinstance(userstories, list):
|
|
raise ValidationError(_("Invalid user stories format"))
|
|
|
|
scales = map(lambda x: x['id'], attrs["scales"])
|
|
|
|
for us in userstories:
|
|
if "id" not in us or "scale_id" not in us:
|
|
raise ValidationError(_("Invalid user story format"))
|
|
|
|
if project.user_stories.filter(id=us['id']).count() == 0:
|
|
raise ValidationError(_("Invalid user story for the project"))
|
|
|
|
if us['scale_id'] is not None and us['scale_id'] not in scales:
|
|
raise ValidationError(_("Invalid scale id for user story"))
|
|
|
|
return attrs
|