Refactor day: Move RoleViewSet from taiga.projects to taiga.users

remotes/origin/enhancement/email-actions
David Barragán Merino 2015-02-10 15:24:08 +01:00
parent 589490d7a5
commit 5196f1c25d
10 changed files with 213 additions and 134 deletions

View File

@ -17,15 +17,13 @@ import operator
from functools import reduce from functools import reduce
import logging import logging
from django.apps import apps
from django.db.models import Q from django.db.models import Q
from django.db.models.sql.where import ExtraWhere, OR, AND
from rest_framework import filters from rest_framework import filters
from taiga.base import tags
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.projects.models import Membership
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -101,7 +99,8 @@ class PermissionBasedFilterBackend(FilterBackend):
try: try:
project_id = int(request.QUERY_PARAMS["project"]) project_id = int(request.QUERY_PARAMS["project"])
except: except:
logger.error("Filtering project diferent value than an integer: {}".format(request.QUERY_PARAMS["project"])) logger.error("Filtering project diferent value than an integer: {}".format(
request.QUERY_PARAMS["project"]))
raise exc.BadRequest("'project' must be an integer value.") raise exc.BadRequest("'project' must be an integer value.")
qs = queryset qs = queryset
@ -109,7 +108,8 @@ class PermissionBasedFilterBackend(FilterBackend):
if request.user.is_authenticated() and request.user.is_superuser: if request.user.is_authenticated() and request.user.is_superuser:
qs = qs qs = qs
elif request.user.is_authenticated(): elif request.user.is_authenticated():
memberships_qs = Membership.objects.filter(user=request.user) membership_model = apps.get_model('projects', 'Membership')
memberships_qs = membership_model.objects.filter(user=request.user)
if project_id: if project_id:
memberships_qs = memberships_qs.filter(project_id=project_id) memberships_qs = memberships_qs.filter(project_id=project_id)
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) | memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) |
@ -187,7 +187,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
try: try:
project_id = int(request.QUERY_PARAMS["project"]) project_id = int(request.QUERY_PARAMS["project"])
except: except:
logger.error("Filtering project diferent value than an integer: {}".format(request.QUERY_PARAMS["project"])) logger.error("Filtering project diferent value than an integer: {}".format(
request.QUERY_PARAMS["project"]))
raise exc.BadRequest("'project' must be an integer value.") raise exc.BadRequest("'project' must be an integer value.")
qs = queryset qs = queryset
@ -195,7 +196,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
if request.user.is_authenticated() and request.user.is_superuser: if request.user.is_authenticated() and request.user.is_superuser:
qs = qs qs = qs
elif request.user.is_authenticated(): elif request.user.is_authenticated():
memberships_qs = Membership.objects.filter(user=request.user) membership_model = apps.get_model("projects", "Membership")
memberships_qs = membership_model.objects.filter(user=request.user)
if project_id: if project_id:
memberships_qs = memberships_qs.filter(project_id=project_id) memberships_qs = memberships_qs.filter(project_id=project_id)
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=['view_project']) | memberships_qs = memberships_qs.filter(Q(role__permissions__contains=['view_project']) |
@ -203,7 +205,8 @@ class CanViewProjectObjFilterBackend(FilterBackend):
projects_list = [membership.project_id for membership in memberships_qs] projects_list = [membership.project_id for membership in memberships_qs]
qs = qs.filter(Q(id__in=projects_list) | Q(public_permissions__contains=["view_project"])) qs = qs.filter((Q(id__in=projects_list) |
Q(public_permissions__contains=["view_project"])))
else: else:
qs = qs.filter(public_permissions__contains=["view_project"]) qs = qs.filter(public_permissions__contains=["view_project"])
@ -221,6 +224,25 @@ class IsProjectMemberFilterBackend(FilterBackend):
return super().filter_queryset(request, queryset.distinct(), view) return super().filter_queryset(request, queryset.distinct(), view)
class MembersFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
project_id = request.QUERY_PARAMS.get('project', None)
if project_id:
project_model = apps.get_model('projects', 'Project')
project = get_object_or_404(project_model, pk=project_id)
if (request.user.is_authenticated() and
project.memberships.filter(user=request.user).exists()):
return queryset.filter(memberships__project=project).distinct()
else:
raise exc.PermissionDenied(_("You don't have permisions to see this project users."))
if request.user.is_superuser:
return queryset
return []
class BaseIsProjectAdminFilterBackend(object): class BaseIsProjectAdminFilterBackend(object):
def get_project_ids(self, request, view): def get_project_ids(self, request, view):
project_id = None project_id = None
@ -233,7 +255,8 @@ class BaseIsProjectAdminFilterBackend(object):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return [] return []
memberships_qs = Membership.objects.filter(user=request.user, is_owner=True) membership_model = apps.get_model('projects', 'Membership')
memberships_qs = membership_model.objects.filter(user=request.user, is_owner=True)
if project_id: if project_id:
memberships_qs = memberships_qs.filter(project_id=project_id) memberships_qs = memberships_qs.filter(project_id=project_id)

View File

@ -20,7 +20,8 @@ from django.db.models import signals
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from taiga.base import filters, response from taiga.base import filters
from taiga.base import response
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.decorators import list_route from taiga.base.decorators import list_route
from taiga.base.decorators import detail_route from taiga.base.decorators import detail_route
@ -32,7 +33,6 @@ from taiga.base.utils.slug import slugify_uniquely
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
from taiga.users.models import Role
from taiga.projects.userstories.models import UserStory from taiga.projects.userstories.models import UserStory
from taiga.projects.tasks.models import Task from taiga.projects.tasks.models import Task
from taiga.projects.issues.models import Issue from taiga.projects.issues.models import Issue
@ -325,7 +325,7 @@ class ProjectTemplateViewSet(ModelCrudViewSet):
###################################################### ######################################################
## Members Invitations and Roles ## Members & Invitations
###################################################### ######################################################
class MembershipViewSet(ModelCrudViewSet): class MembershipViewSet(ModelCrudViewSet):
@ -403,20 +403,3 @@ class InvitationViewSet(ModelListViewSet):
def list(self, *args, **kwargs): def list(self, *args, **kwargs):
raise exc.PermissionDenied(_("You don't have permisions to see that.")) raise exc.PermissionDenied(_("You don't have permisions to see that."))
class RolesViewSet(ModelCrudViewSet):
model = Role
serializer_class = serializers.RoleSerializer
permission_classes = (permissions.RolesPermission, )
filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',)
def pre_delete(self, obj):
move_to = self.request.QUERY_PARAMS.get('moveTo', None)
if move_to:
role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
qs = models.Membership.objects.filter(project_id=obj.project.pk, role=obj)
qs.update(role=role_dest)
super().pre_delete(obj)

View File

@ -16,9 +16,13 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm, from taiga.base.api.permissions import TaigaResourcePermission
IsAuthenticated, IsProjectOwner, from taiga.base.api.permissions import HasProjectPerm
AllowAny, IsSuperUser, PermissionComponent) from taiga.base.api.permissions import IsAuthenticated
from taiga.base.api.permissions import IsProjectOwner
from taiga.base.api.permissions import AllowAny
from taiga.base.api.permissions import IsSuperUser
from taiga.base.api.permissions import PermissionComponent
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.projects.models import Membership from taiga.projects.models import Membership
@ -32,8 +36,8 @@ class CanLeaveProject(PermissionComponent):
try: try:
if not services.can_user_leave_project(request.user, obj): if not services.can_user_leave_project(request.user, obj):
raise exc.PermissionDenied(_("You can't leave the project if there are no more owners")) raise exc.PermissionDenied(_("You can't leave the project if there are no "
"more owners"))
return True return True
except Membership.DoesNotExist: except Membership.DoesNotExist:
return False return False
@ -140,14 +144,6 @@ class IssueTypePermission(TaigaResourcePermission):
bulk_update_order_perms = IsProjectOwner() bulk_update_order_perms = IsProjectOwner()
class RolesPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectOwner()
update_perms = IsProjectOwner()
destroy_perms = IsProjectOwner()
list_perms = AllowAny()
# Project Templates # Project Templates
class ProjectTemplatePermission(TaigaResourcePermission): class ProjectTemplatePermission(TaigaResourcePermission):

View File

@ -14,27 +14,32 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from os import path
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q from django.db.models import Q
from rest_framework import serializers from rest_framework import serializers
from taiga.base.serializers import JsonField, PgArrayField, ModelSerializer, TagsColorsField from taiga.base.serializers import JsonField
from taiga.users.models import Role, User from taiga.base.serializers import PgArrayField
from taiga.base.serializers import ModelSerializer
from taiga.base.serializers import TagsColorsField
from taiga.users.services import get_photo_or_gravatar_url from taiga.users.services import get_photo_or_gravatar_url
from taiga.users.serializers import UserSerializer from taiga.users.serializers import UserSerializer
from taiga.users.serializers import ProjectRoleSerializer
from taiga.users.validators import RoleExistsValidator from taiga.users.validators import RoleExistsValidator
from taiga.permissions.service import get_user_project_permissions, is_project_owner from taiga.permissions.service import get_user_project_permissions
from taiga.permissions.service import is_project_owner
from . import models from . import models
from . import services from . import services
from . validators import ProjectExistsValidator from . validators import ProjectExistsValidator
# User Stories common serializers ######################################################
## Custom values for selectors
######################################################
class PointsSerializer(ModelSerializer): class PointsSerializer(ModelSerializer):
class Meta: class Meta:
@ -69,10 +74,12 @@ class UserStoryStatusSerializer(ModelSerializer):
qs = None qs = None
# If the user story status exists: # If the user story status exists:
if self.object and attrs.get("name", None): if self.object and attrs.get("name", None):
qs = models.UserStoryStatus.objects.filter(project=self.object.project, name=attrs[source]) 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): 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]) qs = models.UserStoryStatus.objects.filter(project=attrs["project"],
name=attrs[source])
if qs and qs.exists(): if qs and qs.exists():
raise serializers.ValidationError("Name duplicated for the project") raise serializers.ValidationError("Name duplicated for the project")
@ -80,8 +87,6 @@ class UserStoryStatusSerializer(ModelSerializer):
return attrs return attrs
# Task common serializers
class TaskStatusSerializer(ModelSerializer): class TaskStatusSerializer(ModelSerializer):
class Meta: class Meta:
model = models.TaskStatus model = models.TaskStatus
@ -103,7 +108,6 @@ class TaskStatusSerializer(ModelSerializer):
return attrs return attrs
# Issues common serializers
class SeveritySerializer(ModelSerializer): class SeveritySerializer(ModelSerializer):
class Meta: class Meta:
@ -142,13 +146,16 @@ class IssueTypeSerializer(ModelSerializer):
model = models.IssueType model = models.IssueType
# Projects ######################################################
## Members
######################################################
class MembershipSerializer(ModelSerializer): class MembershipSerializer(ModelSerializer):
role_name = serializers.CharField(source='role.name', required=False, read_only=True) role_name = serializers.CharField(source='role.name', required=False, read_only=True)
full_name = serializers.CharField(source='user.get_full_name', required=False, read_only=True) full_name = serializers.CharField(source='user.get_full_name', required=False, read_only=True)
user_email = serializers.EmailField(source='user.email', required=False, read_only=True) user_email = serializers.EmailField(source='user.email', required=False, read_only=True)
is_user_active = serializers.BooleanField(source='user.is_active', required=False, read_only=True) is_user_active = serializers.BooleanField(source='user.is_active', required=False,
read_only=True)
email = serializers.EmailField(required=True) email = serializers.EmailField(required=True)
color = serializers.CharField(source='user.color', required=False, read_only=True) color = serializers.CharField(source='user.color', required=False, read_only=True)
photo = serializers.SerializerMethodField("get_photo") photo = serializers.SerializerMethodField("get_photo")
@ -210,7 +217,8 @@ class MembershipSerializer(ModelSerializer):
if project is None: if project is None:
project = self.object.project project = self.object.project
if self.object and not services.project_has_valid_owners(project, exclude_user=self.object.user): if (self.object and
not services.project_has_valid_owners(project, exclude_user=self.object.user)):
raise serializers.ValidationError(_("At least one of the user must be an active admin")) raise serializers.ValidationError(_("At least one of the user must be an active admin"))
return attrs return attrs
@ -229,6 +237,21 @@ class ProjectMembershipSerializer(ModelSerializer):
return get_photo_or_gravatar_url(project.user) return get_photo_or_gravatar_url(project.user)
class MemberBulkSerializer(RoleExistsValidator, serializers.Serializer):
email = serializers.EmailField()
role_id = serializers.IntegerField()
class MembersBulkSerializer(ProjectExistsValidator, serializers.Serializer):
project_id = serializers.IntegerField()
bulk_memberships = MemberBulkSerializer(many=True)
invitation_extra_text = serializers.CharField(required=False, max_length=255)
######################################################
## Projects
######################################################
class ProjectSerializer(ModelSerializer): class ProjectSerializer(ModelSerializer):
tags = PgArrayField(required=False) tags = PgArrayField(required=False)
anon_permissions = PgArrayField(required=False) anon_permissions = PgArrayField(required=False)
@ -300,23 +323,20 @@ class ProjectDetailSerializer(ProjectSerializer):
return serializer.data return serializer.data
class ProjectRoleSerializer(ModelSerializer): ######################################################
## Starred
######################################################
class StarredSerializer(ModelSerializer):
class Meta: class Meta:
model = Role model = models.Project
fields = ('id', 'name', 'slug', 'order', 'computable') fields = ['id', 'name', 'slug']
class RoleSerializer(ModelSerializer):
members_count = serializers.SerializerMethodField("get_members_count")
permissions = PgArrayField(required=False)
class Meta:
model = Role
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order', 'members_count')
def get_members_count(self, obj):
return obj.memberships.count()
######################################################
## Project Templates
######################################################
class ProjectTemplateSerializer(ModelSerializer): class ProjectTemplateSerializer(ModelSerializer):
default_options = JsonField(required=False, label=_("Default options")) default_options = JsonField(required=False, label=_("Default options"))
@ -332,20 +352,3 @@ class ProjectTemplateSerializer(ModelSerializer):
class Meta: class Meta:
model = models.ProjectTemplate model = models.ProjectTemplate
read_only_fields = ("created_date", "modified_date") read_only_fields = ("created_date", "modified_date")
class StarredSerializer(ModelSerializer):
class Meta:
model = models.Project
fields = ['id', 'name', 'slug']
class MemberBulkSerializer(RoleExistsValidator, serializers.Serializer):
email = serializers.EmailField()
role_id = serializers.IntegerField()
class MembersBulkSerializer(ProjectExistsValidator, serializers.Serializer):
project_id = serializers.IntegerField()
bulk_memberships = MemberBulkSerializer(many=True)
invitation_extra_text = serializers.CharField(required=False, max_length=255)

View File

@ -18,15 +18,18 @@ from taiga.base import routers
router = routers.DefaultRouter(trailing_slash=False) router = routers.DefaultRouter(trailing_slash=False)
# taiga.users
from taiga.users.api import UsersViewSet # Users & Roles
from taiga.auth.api import AuthViewSet from taiga.auth.api import AuthViewSet
from taiga.users.api import UsersViewSet
from taiga.users.api import RolesViewSet
router.register(r"users", UsersViewSet, base_name="users")
router.register(r"auth", AuthViewSet, base_name="auth") router.register(r"auth", AuthViewSet, base_name="auth")
router.register(r"users", UsersViewSet, base_name="users")
router.register(r"roles", RolesViewSet, base_name="roles")
#taiga.userstorage # User Storage
from taiga.userstorage.api import StorageEntriesViewSet from taiga.userstorage.api import StorageEntriesViewSet
router.register(r"user-storage", StorageEntriesViewSet, base_name="user-storage") router.register(r"user-storage", StorageEntriesViewSet, base_name="user-storage")
@ -51,8 +54,7 @@ router.register(r"importer", ProjectImporterViewSet, base_name="importer")
router.register(r"exporter", ProjectExporterViewSet, base_name="exporter") router.register(r"exporter", ProjectExporterViewSet, base_name="exporter")
# Projects & Types # Projects & Selectors
from taiga.projects.api import RolesViewSet
from taiga.projects.api import ProjectViewSet from taiga.projects.api import ProjectViewSet
from taiga.projects.api import MembershipViewSet from taiga.projects.api import MembershipViewSet
from taiga.projects.api import InvitationViewSet from taiga.projects.api import InvitationViewSet
@ -65,8 +67,6 @@ from taiga.projects.api import PriorityViewSet
from taiga.projects.api import SeverityViewSet from taiga.projects.api import SeverityViewSet
from taiga.projects.api import ProjectTemplateViewSet from taiga.projects.api import ProjectTemplateViewSet
router.register(r"roles", RolesViewSet, base_name="roles")
router.register(r"projects", ProjectViewSet, base_name="projects") router.register(r"projects", ProjectViewSet, base_name="projects")
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates") router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
router.register(r"memberships", MembershipViewSet, base_name="memberships") router.register(r"memberships", MembershipViewSet, base_name="memberships")
@ -79,22 +79,27 @@ router.register(r"issue-types", IssueTypeViewSet, base_name="issue-types")
router.register(r"priorities", PriorityViewSet, base_name="priorities") router.register(r"priorities", PriorityViewSet, base_name="priorities")
router.register(r"severities",SeverityViewSet , base_name="severities") router.register(r"severities",SeverityViewSet , base_name="severities")
# Attachments # Attachments
from taiga.projects.attachments.api import UserStoryAttachmentViewSet from taiga.projects.attachments.api import UserStoryAttachmentViewSet
from taiga.projects.attachments.api import IssueAttachmentViewSet from taiga.projects.attachments.api import IssueAttachmentViewSet
from taiga.projects.attachments.api import TaskAttachmentViewSet from taiga.projects.attachments.api import TaskAttachmentViewSet
from taiga.projects.attachments.api import WikiAttachmentViewSet from taiga.projects.attachments.api import WikiAttachmentViewSet
router.register(r"userstories/attachments", UserStoryAttachmentViewSet, base_name="userstory-attachments") router.register(r"userstories/attachments", UserStoryAttachmentViewSet,
base_name="userstory-attachments")
router.register(r"tasks/attachments", TaskAttachmentViewSet, base_name="task-attachments") router.register(r"tasks/attachments", TaskAttachmentViewSet, base_name="task-attachments")
router.register(r"issues/attachments", IssueAttachmentViewSet, base_name="issue-attachments") router.register(r"issues/attachments", IssueAttachmentViewSet, base_name="issue-attachments")
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments") router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
# Webhooks # Webhooks
from taiga.webhooks.api import WebhookViewSet, WebhookLogViewSet from taiga.webhooks.api import WebhookViewSet, WebhookLogViewSet
router.register(r"webhooks", WebhookViewSet, base_name="webhooks") router.register(r"webhooks", WebhookViewSet, base_name="webhooks")
router.register(r"webhooklogs", WebhookLogViewSet, base_name="webhooklogs") router.register(r"webhooklogs", WebhookLogViewSet, base_name="webhooklogs")
# History & Components # History & Components
from taiga.projects.history.api import UserStoryHistory from taiga.projects.history.api import UserStoryHistory
from taiga.projects.history.api import TaskHistory from taiga.projects.history.api import TaskHistory
@ -131,22 +136,30 @@ router.register(r"issues/(?P<issue_id>\d+)/voters", VotersViewSet, base_name="is
router.register(r"wiki", WikiViewSet, base_name="wiki") router.register(r"wiki", WikiViewSet, base_name="wiki")
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links") router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
# Notify policies # Notify policies
from taiga.projects.notifications.api import NotifyPolicyViewSet from taiga.projects.notifications.api import NotifyPolicyViewSet
router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications") router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications")
# GitHub webhooks # GitHub webhooks
from taiga.hooks.github.api import GitHubViewSet from taiga.hooks.github.api import GitHubViewSet
router.register(r"github-hook", GitHubViewSet, base_name="github-hook") router.register(r"github-hook", GitHubViewSet, base_name="github-hook")
# Gitlab webhooks # Gitlab webhooks
from taiga.hooks.gitlab.api import GitLabViewSet from taiga.hooks.gitlab.api import GitLabViewSet
router.register(r"gitlab-hook", GitLabViewSet, base_name="gitlab-hook") router.register(r"gitlab-hook", GitLabViewSet, base_name="gitlab-hook")
# Bitbucket webhooks # Bitbucket webhooks
from taiga.hooks.bitbucket.api import BitBucketViewSet from taiga.hooks.bitbucket.api import BitBucketViewSet
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook") router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook")
# feedback # feedback
# - see taiga.feedback.routers and taiga.feedback.apps # - see taiga.feedback.routers and taiga.feedback.apps

View File

@ -26,20 +26,21 @@ from django.conf import settings
from easy_thumbnails.source_generators import pil_image from easy_thumbnails.source_generators import pil_image
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.filters import BaseFilterBackend
from rest_framework import status from rest_framework import status
from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail from djmail.template_mail import MagicMailBuilder
from djmail.template_mail import InlineCSSTemplateMail
from taiga.auth.tokens import get_user_for_token from taiga.auth.tokens import get_user_for_token
from taiga.base.decorators import list_route, detail_route from taiga.base.decorators import list_route
from taiga.base.decorators import detail_route
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base import filters
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.base.api.utils import get_object_or_404 from taiga.base.api.utils import get_object_or_404
from taiga.base.utils.slug import slugify_uniquely from taiga.base.filters import MembersFilterBackend
from taiga.projects.votes import services as votes_service from taiga.projects.votes import services as votes_service
from taiga.projects.serializers import StarredSerializer from taiga.projects.serializers import StarredSerializer
from taiga.permissions.service import is_project_owner
from . import models from . import models
from . import serializers from . import serializers
@ -47,22 +48,9 @@ from . import permissions
from .signals import user_cancel_account as user_cancel_account_signal from .signals import user_cancel_account as user_cancel_account_signal
class MembersFilterBackend(BaseFilterBackend): ######################################################
def filter_queryset(self, request, queryset, view): ## User
project_id = request.QUERY_PARAMS.get('project', None) ######################################################
if project_id:
Project = apps.get_model('projects', 'Project')
project = get_object_or_404(Project, pk=project_id)
if request.user.is_authenticated() and project.memberships.filter(user=request.user).exists():
return queryset.filter(memberships__project=project).distinct()
else:
raise exc.PermissionDenied(_("You don't have permisions to see this project users."))
if request.user.is_superuser:
return queryset
return []
class UsersViewSet(ModelCrudViewSet): class UsersViewSet(ModelCrudViewSet):
permission_classes = (permissions.UserPermission,) permission_classes = (permissions.UserPermission,)
@ -73,7 +61,9 @@ class UsersViewSet(ModelCrudViewSet):
raise exc.NotSupported() raise exc.NotSupported()
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
self.object_list = MembersFilterBackend().filter_queryset(request, self.get_queryset(), self) self.object_list = MembersFilterBackend().filter_queryset(request,
self.get_queryset(),
self)
page = self.paginate_queryset(self.object_list) page = self.paginate_queryset(self.object_list)
if page is not None: if page is not None:
@ -249,12 +239,14 @@ class UsersViewSet(ModelCrudViewSet):
""" """
serializer = serializers.ChangeEmailSerializer(data=request.DATA, many=False) serializer = serializers.ChangeEmailSerializer(data=request.DATA, many=False)
if not serializer.is_valid(): if not serializer.is_valid():
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you didn't use it before?")) raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
"didn't use it before?"))
try: try:
user = models.User.objects.get(email_token=serializer.data["email_token"]) user = models.User.objects.get(email_token=serializer.data["email_token"])
except models.User.DoesNotExist: except models.User.DoesNotExist:
raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you didn't use it before?")) raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
"didn't use it before?"))
self.check_permissions(request, "change_email", user) self.check_permissions(request, "change_email", user)
user.email = user.new_email user.email = user.new_email
@ -304,3 +296,25 @@ class UsersViewSet(ModelCrudViewSet):
user_cancel_account_signal.send(sender=user.__class__, user=user, request_data=request_data) user_cancel_account_signal.send(sender=user.__class__, user=user, request_data=request_data)
user.cancel() user.cancel()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
######################################################
## Role
######################################################
class RolesViewSet(ModelCrudViewSet):
model = models.Role
serializer_class = serializers.RoleSerializer
permission_classes = (permissions.RolesPermission, )
filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',)
def pre_delete(self, obj):
move_to = self.request.QUERY_PARAMS.get('moveTo', None)
if move_to:
membership_model = apps.get_model("projects", "Membership")
role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
qs = membership_model.objects.filter(project_id=obj.project.pk, role=obj)
qs.update(role=role_dest)
super().pre_delete(obj)

View File

@ -174,6 +174,7 @@ class User(AbstractBaseUser, PermissionsMixin):
self.save() self.save()
self.auth_data.all().delete() self.auth_data.all().delete()
class Role(models.Model): class Role(models.Model):
name = models.CharField(max_length=200, null=False, blank=False, name = models.CharField(max_length=200, null=False, blank=False,
verbose_name=_("name")) verbose_name=_("name"))

View File

@ -14,9 +14,13 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (TaigaResourcePermission, IsSuperUser, from taiga.base.api.permissions import TaigaResourcePermission
AllowAny, PermissionComponent, from taiga.base.api.permissions import IsSuperUser
IsAuthenticated) from taiga.base.api.permissions import AllowAny
from taiga.base.api.permissions import IsAuthenticated
from taiga.base.api.permissions import HasProjectPerm
from taiga.base.api.permissions import IsProjectOwner
from taiga.base.api.permissions import PermissionComponent
class IsTheSameUser(PermissionComponent): class IsTheSameUser(PermissionComponent):
@ -39,3 +43,11 @@ class UserPermission(TaigaResourcePermission):
remove_avatar_perms = IsAuthenticated() remove_avatar_perms = IsAuthenticated()
starred_perms = AllowAny() starred_perms = AllowAny()
change_email_perms = IsTheSameUser() change_email_perms = IsTheSameUser()
class RolesPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectOwner()
update_perms = IsProjectOwner()
destroy_perms = IsProjectOwner()
list_perms = AllowAny()

View File

@ -16,16 +16,24 @@
from django.core import validators from django.core import validators
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from taiga.base.serializers import Serializer
from taiga.base.serializers import ModelSerializer
from taiga.base.serializers import PgArrayField
from .models import User from .models import User, Role
from .services import get_photo_or_gravatar_url, get_big_photo_or_gravatar_url from .services import get_photo_or_gravatar_url, get_big_photo_or_gravatar_url
import re import re
class UserSerializer(serializers.ModelSerializer): ######################################################
## User
######################################################
class UserSerializer(ModelSerializer):
full_name_display = serializers.SerializerMethodField("get_full_name_display") full_name_display = serializers.SerializerMethodField("get_full_name_display")
photo = serializers.SerializerMethodField("get_photo") photo = serializers.SerializerMethodField("get_photo")
big_photo = serializers.SerializerMethodField("get_big_photo") big_photo = serializers.SerializerMethodField("get_big_photo")
@ -39,16 +47,19 @@ class UserSerializer(serializers.ModelSerializer):
def validate_username(self, attrs, source): def validate_username(self, attrs, source):
value = attrs[source] value = attrs[source]
validator = validators.RegexValidator(re.compile('^[\w.-]+$'), "invalid username", "invalid") validator = validators.RegexValidator(re.compile('^[\w.-]+$'), _("invalid username"),
_("invalid"))
try: try:
validator(value) validator(value)
except ValidationError: except ValidationError:
raise serializers.ValidationError("Required. 255 characters or fewer. Letters, numbers " raise serializers.ValidationError(_("Required. 255 characters or fewer. Letters, "
"and /./-/_ characters'") "numbers and /./-/_ characters'"))
if self.object and self.object.username != value and User.objects.filter(username=value).exists(): if (self.object and
raise serializers.ValidationError("Invalid username. Try with a different one.") self.object.username != value and
User.objects.filter(username=value).exists()):
raise serializers.ValidationError(_("Invalid username. Try with a different one."))
return attrs return attrs
@ -62,14 +73,36 @@ class UserSerializer(serializers.ModelSerializer):
return get_big_photo_or_gravatar_url(user) return get_big_photo_or_gravatar_url(user)
class RecoverySerializer(serializers.Serializer): class RecoverySerializer(Serializer):
token = serializers.CharField(max_length=200) token = serializers.CharField(max_length=200)
password = serializers.CharField(min_length=6) password = serializers.CharField(min_length=6)
class ChangeEmailSerializer(serializers.Serializer): class ChangeEmailSerializer(Serializer):
email_token = serializers.CharField(max_length=200) email_token = serializers.CharField(max_length=200)
class CancelAccountSerializer(serializers.Serializer): class CancelAccountSerializer(Serializer):
cancel_token = serializers.CharField(max_length=200) cancel_token = serializers.CharField(max_length=200)
######################################################
## Role
######################################################
class RoleSerializer(ModelSerializer):
members_count = serializers.SerializerMethodField("get_members_count")
permissions = PgArrayField(required=False)
class Meta:
model = Role
fields = ('id', 'name', 'permissions', 'computable', 'project', 'order', 'members_count')
def get_members_count(self, obj):
return obj.memberships.count()
class ProjectRoleSerializer(ModelSerializer):
class Meta:
model = Role
fields = ('id', 'name', 'slug', 'order', 'computable')

View File

@ -2,6 +2,7 @@ from django.core.urlresolvers import reverse
from taiga.base.utils import json from taiga.base.utils import json
from taiga.projects import serializers from taiga.projects import serializers
from taiga.users.serializers import RoleSerializer
from taiga.permissions.permissions import MEMBERS_PERMISSIONS from taiga.permissions.permissions import MEMBERS_PERMISSIONS
from tests import factories as f from tests import factories as f
@ -140,19 +141,19 @@ def test_roles_update(client, data):
data.project_owner data.project_owner
] ]
role_data = serializers.RoleSerializer(data.public_project.roles.all()[0]).data role_data = RoleSerializer(data.public_project.roles.all()[0]).data
role_data["name"] = "test" role_data["name"] = "test"
role_data = json.dumps(role_data) role_data = json.dumps(role_data)
results = helper_test_http_method(client, 'put', public_url, role_data, users) results = helper_test_http_method(client, 'put', public_url, role_data, users)
assert results == [401, 403, 403, 403, 200] assert results == [401, 403, 403, 403, 200]
role_data = serializers.RoleSerializer(data.private_project1.roles.all()[0]).data role_data = RoleSerializer(data.private_project1.roles.all()[0]).data
role_data["name"] = "test" role_data["name"] = "test"
role_data = json.dumps(role_data) role_data = json.dumps(role_data)
results = helper_test_http_method(client, 'put', private1_url, role_data, users) results = helper_test_http_method(client, 'put', private1_url, role_data, users)
assert results == [401, 403, 403, 403, 200] assert results == [401, 403, 403, 403, 200]
role_data = serializers.RoleSerializer(data.private_project2.roles.all()[0]).data role_data = RoleSerializer(data.private_project2.roles.all()[0]).data
role_data["name"] = "test" role_data["name"] = "test"
role_data = json.dumps(role_data) role_data = json.dumps(role_data)
results = helper_test_http_method(client, 'put', private2_url, role_data, users) results = helper_test_http_method(client, 'put', private2_url, role_data, users)